Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

오류와 린트

rustc가 훌륭한 오류 메시지를 제공하도록 많은 노력을 기울여 왔습니다. 이 장에서는 컴파일러에서 컴파일 오류와 린트를 내보내는 방법을 다룹니다.

진단 구조

진단 오류의 주요 구성 요소는 다음과 같습니다:

error[E0000]: main error message
  --> file.rs:LL:CC
   |
LL | <code>
   | -^^^^- secondary label
   |  |
   |  primary label
   |
   = note: note without a `Span`, created with `.note`
note: sub-diagnostic message for `.span_note`
  --> file.rs:LL:CC
   |
LL | more code
   |      ^^^^
  • 수준(error, warning 등). 메시지의 심각도를 나타냅니다. (진단 수준을 참고하십시오)
  • 코드(예를 들어 “mismatched types“의 경우 E0308). 오류 코드 색인에 있는 문제의 확장 설명을 통해 사용자가 현재 오류에 대한 더 많은 정보를 얻을 수 있도록 돕습니다. 모든 진단에 코드가 있는 것은 아닙니다. 예를 들어 린트가 생성한 진단에는 코드가 없습니다.
  • 메시지. 문제에 대한 주된 설명입니다. 메시지는 따로 떼어 놓고 보아도 의미가 통하도록 일반적이어야 하며 그 자체로 완결되어야 합니다.
  • 진단 창. 진단 창에는 여러 요소가 포함됩니다:
    • 주 스팬이 시작되는 위치의 경로, 줄 번호, 열 번호.
    • 영향을 받는 사용자 코드와 그 주변 코드.
    • 사용자 코드 아래에 표시되는 주 스팬과 보조 스팬. 이 스팬들은 선택적으로 하나 이상의 레이블을 포함할 수 있습니다.
      • 주 스팬에는 그것만 표시되는 경우(예를 들어 IDE에서)에도 의미가 통하도록 문제를 설명하기에 충분한 텍스트가 있어야 합니다. 주 스팬은 “공간을 인식“하므로(코드를 가리키므로) 일반적으로 오류 메시지보다 더 간결하게 쓸 수 있습니다.
      • 여러 스팬 레이블이 겹쳐 출력이 어수선해질 것으로 예상된다면 출력을 적절히 조정하는 것이 좋습니다. 예를 들어 if/else arms have incompatible types 오류는 모든 갈래가 같은 줄에 있는지, 갈래 중 하나가 비어 있는지, 또는 두 경우 모두 해당하지 않는지에 따라 서로 다른 스팬을 사용합니다.
  • 하위 진단. 모든 오류는 오류의 주요 부분과 비슷해 보이는 하위 진단을 여러 개 가질 수 있습니다. 하위 진단은 설명의 순서가 코드의 순서와 일치하지 않을 수 있는 경우에 사용됩니다. 설명의 순서에 제약이 없다면 주 진단에서 보조 레이블을 활용하는 편이 좋습니다. 일반적으로 덜 장황하기 때문입니다.

텍스트는 사실만 담백하게 서술해야 하며, 여러 문장이 필요한 경우가 아니라면 대문자와 마침표를 쓰지 않아야 합니다:

error: the fobrulator needs to be krontrificated

메시지나 레이블에 코드나 식별자를 넣어야 할 때는 백틱으로 감싸야 합니다:

error: the identifier `foo.bar` is invalid

오류 코드와 설명

대부분의 오류에는 관련된 오류 코드가 있습니다. 오류 코드는 긴 형식의 설명과 연결되어 있으며, 이 설명에는 해당 오류를 발생시키는 방법에 대한 예제와 오류에 대한 상세한 내용이 담겨 있습니다. 이 설명은 --explain 플래그나 오류 색인을 통해 볼 수 있습니다.

일반적으로, 설명이 오류 자체보다 더 많은 정보를 제공한다면 오류에 (관련 설명과 함께) 코드를 부여하십시오. 대부분의 경우에는 모든 정보를 출력되는 오류 자체에 담는 것이 더 좋습니다. 하지만 그렇게 하면 오류가 장황해지거나, 발생 원인이 너무 많아서 모든 경우에 유용한 정보를 오류에 담을 수 없는 때도 있으며, 이런 경우에는 설명을 추가하는 것이 좋습니다.1 늘 그렇듯이, 확실하지 않다면 리뷰어에게 물어보십시오!

관련 오류 코드가 있는 새 오류를 추가하기로 했다면, 그 절차에 대한 안내와 중요한 세부 사항은 이 절을 읽어 보십시오.

린트와 고정 진단

일부 메시지는 린트를 통해 출력되며, 이때 사용자가 수준을 조절할 수 있습니다. 대부분의 진단은 사용자가 수준을 조절할 수 없도록 하드코딩되어 있습니다.

보통은 진단을 “고정“으로 할지 린트로 할지가 분명하지만, 애매한 영역도 있습니다.

다음은 몇 가지 예입니다.

  • 빌림 검사기 오류: 이는 고정 오류입니다. 사용자는 빌림 검사기를 잠재우기 위해 이러한 진단의 수준을 조정할 수 없습니다.
  • 죽은 코드: 이는 린트입니다. 사용자가 자신의 크레이트에 죽은 코드가 있기를 바라지는 않겠지만, 이를 하드 오류로 만들면 리팩터링과 개발이 매우 고통스러워집니다.
  • 향후 비호환 린트: 이는 잠재울 수 있는 린트입니다. 이를 고정 오류로 만들면 너무 많은 코드가 깨질 것이라고 판단했기 때문에, 대신 경고를 출력하며 이 경고는 결국 고정(하드) 오류로 바뀔 것입니다.

일반적인 코드에서는 하드코딩된 경고(span_warn 같은 메서드를 사용하는 경고)를 피하고, 대신 린트를 사용하는 것이 좋습니다. CLI 플래그 관련 경고와 같은 일부 경우에는 하드코딩된 경고를 사용해야 합니다.

고정 오류 대신 오류 수준의 린트를 사용해야 하는 경우에 대한 지침은 아래의 deny 린트 수준을 참고하십시오.

진단 출력 스타일 가이드

  • 평이하고 간단한 영어로 작성하십시오. 메시지를 (한동안 닦지 않은) 작을 수도 있는 화면에 띄웠을 때, 밤새 파티를 하고 막 잠자리에서 일어난 평범한 프로그래머가 이해할 수 없다면 그 메시지는 너무 복잡한 것입니다.
  • Error, Warning, Note, Help 메시지는 소문자로 시작하며 문장 부호로 끝나지 않습니다.
  • 오류 메시지는 간결해야 합니다. 사용자는 이러한 오류 메시지를 여러 번 보게 되며, 더 자세한 설명은 --explain 플래그로 볼 수 있습니다. 그렇다고 해서 이해하기 어려울 정도로 짧게 만들지는 마십시오.
  • “illegal”(불법)이라는 단어는 불법입니다. 대신 “invalid”(유효하지 않은)나 더 구체적인 단어를 사용하십시오.
  • 오류는 발생한 코드의 스팬을 기록해야 합니다(rustc_errors::DiagCtxt의 span_* 메서드나 진단 구조체의 #[primary_span]을 사용하면 쉽게 할 수 있습니다). 또한 스팬이 너무 크지 않다면 오류에 기여한 다른 스팬도 note로 표시하십시오.
  • 스팬과 함께 메시지를 내보낼 때는 문제를 여전히 나타낼 수 있는 범위에서 스팬을 가능한 한 작게 줄이도록 하십시오
  • 같은 오류에 대해 여러 개의 오류 메시지를 내보내지 않도록 하십시오. 이를 위해 중복을 감지해야 할 수도 있습니다.
  • 컴파일러가 특정 오류 메시지를 만들기에 정보가 너무 부족할 때는, 더 많은 정보를 추가할 수 있도록 라이브러리 코드용 새 속성을 추가하는 것에 대해 컴파일러 팀과 상의하십시오. 예를 들어 #[rustc_on_unimplemented]를 참고하십시오. 이러한 어노테이션을 사용할 수 있다면 사용하십시오!
  • Rust의 학습 곡선은 꽤 가파르며, 컴파일러 메시지가 중요한 학습 도구라는 점을 명심하십시오.
  • 컴파일러에 대해 이야기할 때는 Rust나 rustc가 아니라 the compiler라고 부르십시오.
  • 항목을 나열할 때는 옥스퍼드 쉼표를 사용하십시오.
  • 속성을 언급할 때는 가능하면 “the inline attribute“와 같은 형식을 사용하십시오. 관련이 있는 경우가 아니라면 메시지나 스팬에 #[/#![/] 구분자나 속성 인자를 포함하지 마십시오.

린트 이름 짓기

RFC 0344에 따르면, 린트 이름은 다음 지침에 따라 일관되게 지어야 합니다.

기본 규칙은 다음과 같습니다. 린트 이름은 “allow lint-name” 또는 “allow lint-name items“로 읽었을 때 자연스러워야 합니다. 예를 들어 “allow deprecated items“와 “allow dead_code“는 자연스럽지만, “allow unsafe_block“은 문법에 맞지 않습니다(복수형이어야 합니다).

  • 린트 이름은 #[allow(deprecated)] (items)가 올바르게 읽히도록, deprecated처럼 검사 대상이 되는 나쁜 점을 나타내야 합니다. 따라서 ctypes는 적절한 이름이 아니며, improper_ctypes가 적절합니다.

  • (안정성 린트처럼) 임의의 항목에 적용되는 린트는 무엇을 검사하는지만 언급해야 합니다. deprecated_items보다는 deprecated를 사용하십시오. 이렇게 하면 린트 이름이 짧게 유지됩니다. (다시 말하지만, “allow lint-name items“를 떠올리십시오.)

  • 린트가 특정 문법적 범주에 적용된다면 그 범주를 언급하고 복수형을 사용하십시오. unused_variable보다는 unused_variables를 사용하십시오. 이렇게 하면 #[allow(unused_variables)]가 올바르게 읽힙니다.

  • 코드의 불필요하거나, 사용되지 않거나, 쓸모없는 부분을 잡아내는 린트는 unused_imports, unused_typecasts처럼 unused라는 용어를 사용해야 합니다.

  • 함수 이름을 지을 때와 같은 방식으로 스네이크 케이스를 사용하십시오.

진단 수준

각 진단 수준에 대한 지침은 다음과 같습니다.

  • error: 프로그램이 유효하지 않거나 프로그래머가 특정 warning을 오류로 만들기로 하여, 컴파일러가 프로그램을 컴파일할 수 없는 문제를 감지했을 때 내보냅니다.

  • warning: 컴파일러가 프로그램에서 이상한 점을 감지했을 때 내보냅니다. 경고를 추가할 때는 경고 피로를 피하고, 실제로는 코드에 문제가 없는데 경고하는 거짓 양성을 피하도록 주의해야 합니다. 경고를 내보내는 것이 적절한 경우의 몇 가지 예는 다음과 같습니다.

    • 사용 중단된 항목을 교체하거나 Result를 사용하는 것처럼 사용자가 조치를 취해야 하지만, 그 외에는 컴파일을 막지 않는 상황입니다.
    • 코드의 의미에 영향을 주지 않고 제거할 수 있는 불필요한 구문입니다. 예를 들어 사용되지 않는 코드나 불필요한 unsafe가 있습니다.
    • 잘못되었거나 위험하거나 혼란스러울 가능성이 매우 높지만 언어가 기술적으로 허용하며, 아직 오류로 만들 만큼 준비되지 않았거나 확신이 없는 코드입니다. unused_comparisons(범위를 벗어난 비교)나 bindings_with_variant_name(사용자가 패턴에서 바인딩을 만들려는 의도가 아니었을 가능성이 높음)이 그 예입니다.
    • 과거에 실수로 또는 잘못 허용되었지만, 이를 거부하면 생태계에 과도한 파손이 발생하는 향후 비호환 린트입니다.
    • 스타일상의 선택입니다. 예를 들어 카멜 케이스나 스네이크 케이스, 또는 2018 에디션의 dyn 트레이트 경고가 있습니다. 이러한 린트는 추가되기 위한 기준이 높으며, 예외적인 상황에서만 사용해야 합니다. 그 밖의 스타일상의 선택은 기본적으로 허용(allow-by-default)되는 린트로 만들거나, Clippy나 rustfmt 같은 다른 도구의 일부가 되어야 합니다.
  • help: error나 warning 뒤에 출력되어 사용자에게 문제를 해결하는 방법에 대한 추가 정보를 제공합니다. 이러한 메시지에는 도구가 소스를 자동으로 수정할 수 있도록 안내하는 제안 문자열과 rustc_errors::Applicability 신뢰 수준이 포함되는 경우가 많습니다. 자세한 내용은 제안 절을 참고하십시오.

    오류나 경고 부분에서는 문제를 해결하는 방법을 제안하지 않아야 하며, “help” 하위 진단에서만 제안해야 합니다.

  • note: 더 많은 맥락을 제공하고, 경고나 오류를 일으킨 추가적인 상황과 코드 부분을 알려 주기 위해 출력됩니다. 예를 들어 빌림 검사기는 이전에 있었던 충돌하는 빌림을 모두 note로 알려 줍니다.

    help와 note의 차이: help는 사용자가 문제를 해결하기 위해 할 수 있는 변경 사항을 보여 주는 데 사용해야 합니다. note는 다른 맥락, 정보와 사실, 읽어 볼 온라인 자료 등 그 밖의 모든 것에 사용해야 합니다.

린트 수준과 혼동하지 마십시오. 린트 수준의 지침은 다음과 같습니다.

  • forbid: 린트의 기본값은 절대 forbid가 되어서는 안 됩니다.

  • deny: error 진단 수준과 동일합니다. 몇 가지 예는 다음과 같습니다.

    • 경고 수준에서 격상된 향후 비호환 린트 또는 에디션 기반 린트입니다.
    • 잘못되었다는 확신이 극도로 높지만, 그래도 통과시킬 수 있는 탈출구가 필요한 경우입니다.
  • warn: warning 진단 수준과 동일합니다. 지침은 위의 warning을 참고하십시오.

  • allow: 기본값이 allow여야 하는 린트의 종류에 대한 예는 다음과 같습니다.

    • 린트의 거짓 양성 비율이 너무 높은 경우입니다.
    • 린트가 지나치게 주관적인 경우입니다.
    • 린트가 실험적인 경우입니다.
    • 린트가 일반적으로 강제되지 않는 것을 강제하는 데 사용되는 경우입니다. 예를 들어 unsafe_code 린트는 unsafe 코드의 사용을 막는 데 사용할 수 있습니다.

린트 수준에 대한 자세한 정보는 rustc 책과 레퍼런스에서 찾을 수 있습니다.

유용한 팁과 옵션

오류의 원인 찾기

특정 오류가 어디에서 방출되는지 찾는 주요 방법은 세 가지입니다:

  • 오류 메시지/레이블의 일부나 오류 코드로 grep을 하십시오. 이 방법은 대개 잘 동작하고 간단하지만, 오류를 방출하는 코드가 오류를 구성하는 코드로부터 비교적 깊은 호출 스택 너머로 떨어져 있는 경우도 있습니다. 그런 경우에도 이 방법은 방향을 잡는 데 좋은 수단입니다.

  • nightly 전용 플래그 -Z treat-err-as-bug=1을 붙여 rustc를 호출하면 처음 방출되는 오류를 내부 컴파일러 오류(Internal Compiler Error)로 취급하므로, 오류가 방출된 지점의 스택 트레이스를 얻을 수 있습니다. 더 나중의 오류에서 트리거하고 싶다면 1을 다른 값으로 바꾸십시오.

    이 방법에는 다음과 같은 한계가 있습니다:

    • 일부 호출은 컴파일된 rustc에서 인라인되기 때문에 스택 트레이스에서 생략됩니다.
    • 오류가 구성되는 곳이 방출되는 곳과 멀리 떨어져 있어, grep 방식에서 겪었던 것과 비슷한 문제가 생깁니다. 어떤 경우에는 여러 오류를 순서대로 방출하기 위해 버퍼에 모아 둡니다.
  • -Z track-diagnostics를 붙여 rustc를 호출하면 오류와 함께 오류가 생성된 위치를 출력합니다.

일반적인 개발 관행도 적용됩니다: 일이 어떤 순서로 일어나는지 파악하기 위해 debug!() 문을 적절히 사용하고, 디버거로 중단점을 걸어 보십시오.

Span

Span은 rustc에서 컴파일 중인 코드의 위치를 나타내는 데 쓰이는 주요 자료 구조입니다. Span은 HIR과 MIR의 대부분의 구성 요소에 붙어 있어, 더 유익한 오류 보고를 가능하게 합니다.

Span을 SourceMap에서 조회하면, span_to_snippet 및 SourceMap의 다른 유사한 메서드를 사용하여 오류를 표시하는 데 유용한 “스니펫“을 얻을 수 있습니다.

오류 메시지

rustc_errors 크레이트는 오류 보고에 사용되는 유틸리티 대부분을 정의합니다.

진단은 Diagnostic 트레이트를 구현하는 타입으로 구현할 수 있습니다. 이 방식은 진단을 방출하는 로직과 주요 코드 경로를 분리하도록 강제하므로 새 진단에 권장됩니다. 덜 복잡한 진단의 경우 Diagnostic 트레이트를 derive할 수 있습니다 – 진단 구조체를 참고하십시오. 트레이트 구현 안에서는 아래에 설명된 API를 평소처럼 사용할 수 있습니다.

DiagCtxt에는 오류를 생성하고 방출하는 메서드가 있습니다. 이 메서드들은 보통 span_err, struct_span_err, span_warn 등과 같은 이름을 가집니다. 이런 메서드는 매우 많으며, 경고, 오류, 치명적 오류, 제안 등 다양한 종류의 “오류“를 방출합니다.

일반적으로 이러한 메서드는 두 부류로 나뉩니다: 오류를 바로 방출하는 메서드와, 무엇을 방출할지 더 세밀하게 제어할 수 있게 해 주는 메서드입니다. 예를 들어 span_err는 주어진 Span에 주어진 오류 메시지를 방출하지만, struct_span_err는 대신 Diag를 반환합니다.

이 메서드들 대부분은 문자열을 받지만, 새 진단에는 번역 가능한 진단을 위한 타입이 지정된 식별자를 사용하는 것이 권장됩니다(번역 참고).

Diag를 사용하면 emit 메서드를 호출해 오류를 방출하기 전에 관련 노트와 제안을 오류에 추가할 수 있습니다. (Diag를 방출하지도 취소하지도 않으면 ICE가 발생합니다.) 할 수 있는 작업에 대한 자세한 내용은 문서를 참고하십시오.

// Get a `Diag`. This does _not_ emit an error yet.
let mut err = sess.dcx.struct_span_err(sp, fluent::example::example_error);

// In some cases, you might need to check if `sp` is generated by a macro to
// avoid printing weird errors about macro-generated code.

if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
    // Use the snippet to generate a suggested fix
    err.span_suggestion(suggestion_sp, fluent::example::try_qux_suggestion, format!("qux {}", snippet));
} else {
    // If we weren't able to generate a snippet, then emit a "help" message
    // instead of a concrete "suggestion". In practice this is unlikely to be
    // reached.
    err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}

// emit the error
err.emit();
example-example-error = oh no! this is an error!
  .try-qux-suggestion = try using a qux here
  .qux-suggestion = you could use a qux here instead

제안

사용자의 코드가 왜 잘못되었는지 정확히 알려 주는 것에 더해, 이를 어떻게 고칠 수 있는지까지 알려 줄 수 있는 경우도 많습니다. 이를 위해 Diag는 구조화된 제안 API를 제공합니다. 이 API는 코드 제안을 터미널에 보기 좋게 형식화하거나, (--error-format json 플래그가 전달된 경우) rustfix 같은 도구가 사용할 수 있도록 JSON으로 형식화합니다.

모든 제안을 기계적으로 적용해야 하는 것은 아닙니다. 제안된 코드에는 신뢰도가 있으며, 이는 높음(Applicability::MachineApplicable)부터 낮음(Applicability::MaybeIncorrect)까지 다양합니다. 수준을 선택할 때는 보수적으로 판단하십시오. 제안을 만들려면 Diag의 span_suggestion 메서드를 사용하십시오. 마지막 인자는 제안을 기계적으로 적용할 수 있는지 여부에 대한 힌트를 도구에 제공합니다.

제안은 하나 이상의 스팬을 가리키며, 각 스팬에는 현재 내용을 대체할 코드가 대응됩니다.

제안과 함께 표시되는 메시지는 다음과 같은 맥락에서 이해할 수 있어야 합니다.

  • 독립적인 하위 진단으로 표시되는 경우(기본 출력입니다)
  • 영향을 받는 스팬을 가리키는 레이블로 표시되는 경우(장황함에 관한 몇 가지 휴리스틱을 충족하면 자동으로 이렇게 표시됩니다)
  • 내용이 없는 help 하위 진단으로 표시되는 경우(텍스트만으로도 제안이 명확하지만 여전히 도구가 제안을 적용할 수 있게 하려는 경우에 사용합니다)
  • 표시되지 않는 경우(매우 명확하지만 여전히 도구가 제안을 적용할 수 있게 하려는 경우에 사용합니다)

예를 들어 qux 제안을 기계적으로 적용할 수 있게 만들려면 다음과 같이 합니다.

let mut err = sess.dcx.struct_span_err(sp, fluent::example::message);

if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
    err.span_suggestion(
        suggestion_sp,
        fluent::example::try_qux_suggestion,
        format!("qux {}", snippet),
        Applicability::MachineApplicable,
    );
} else {
    err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}

err.emit();

이 코드는 다음과 같은 오류를 출력할 수 있습니다.

$ rustc mycode.rs
error[E0999]: oh no! this is an error!
 --> mycode.rs:3:5
  |
3 |     sad()
  |     ^ help: try using a qux here: `qux sad()`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0999`.

제안이 여러 줄에 걸쳐 있거나 제안이 여러 개인 경우처럼, 어떤 경우에는 제안이 따로 표시됩니다.

error[E0999]: oh no! this is an error!
 --> mycode.rs:3:5
  |
3 |     sad()
  |     ^
help: try using a qux here:
  |
3 |     qux sad()
  |     ^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0999`.

Applicability에 사용할 수 있는 값은 다음과 같습니다.

  • MachineApplicable: 기계적으로 적용할 수 있습니다.
  • HasPlaceholders: 제안에 자리 표시자 텍스트가 포함되어 있으므로 기계적으로 적용할 수 없습니다. 예: try adding a type: `let x: <type>` .
  • MaybeIncorrect: 좋은 제안인지 아닌지 알 수 없으므로 기계적으로 적용할 수 없습니다.
  • Unspecified: 위의 경우 중 어디에 해당하는지 알 수 없으므로 기계적으로 적용할 수 없습니다.

제안 스타일 가이드

  • 제안은 질문 형태여서는 안 됩니다. 특히 “did you mean“과 같은 표현은 피해야 합니다. 때로는 특정 제안이 왜 나왔는지 분명하지 않을 수 있습니다. 이러한 경우에는 제안이 무엇인지 솔직하게 밝히는 것이 좋습니다.

    “did you mean: Foo“와 “there is a struct with a similar name: Foo“를 비교해 보십시오.

  • 메시지에는 “the following”, “as shown“과 같은 문구를 포함해서는 안 됩니다. 무엇에 관한 이야기인지는 스팬을 사용하여 전달하십시오.

  • 메시지에는 “to do xyz, use“나 “to do xyz, use abc“와 같은 추가 안내를 포함할 수 있습니다.

  • 메시지에는 함수, 변수 또는 타입의 이름을 포함할 수 있지만, 표현식 전체를 포함하는 것은 피하십시오.

린트

컴파일러의 린트 인프라는 rustc_middle::lint 모듈에 정의되어 있습니다.

린트는 언제 실행됩니까?

린트마다 작업에 필요한 정보가 다르므로, 린트는 저마다 다른 시점에 실행됩니다. 일부 린트는 패스로 묶이며, 한 패스에 속한 린트들은 하나의 방문자(visitor)를 통해 함께 처리됩니다. 패스에는 다음과 같은 것들이 있습니다.

  • 확장 전 패스: 매크로 확장 전에 AST 노드를 대상으로 동작합니다. 일반적으로 이 패스는 사용하지 않는 것이 좋습니다.

    • 예시: keyword_idents는 향후 에디션에서 키워드가 될 식별자를 검사하지만, 매크로에서 사용된 식별자에 민감합니다.
  • 초기 린트 패스: 매크로 확장과 이름 해석 이후, AST 로워링 직전에 AST 노드를 대상으로 동작합니다. 이 린트들은 순수하게 구문적인 린트를 위한 것입니다.

    • 예시: unused_parens 린트는 if 조건처럼 괄호가 필요하지 않은 상황에서 괄호로 묶인 표현식을 검사합니다.
  • 후기 린트 패스: 분석의 끝 무렵(빌림 검사 이후 등)에 HIR 노드를 대상으로 동작합니다. 이 린트들은 완전한 타입 정보를 사용할 수 있습니다. 대부분의 린트는 후기 린트입니다.

    • 예시: invalid_value 린트(명백히 유효하지 않은 초기화되지 않은 값을 검사함)는 어떤 타입이 초기화되지 않은 상태로 남아도 되는지 판단하는 데 타입 정보가 필요하므로 후기 린트입니다.
  • MIR 패스: MIR 노드를 대상으로 동작합니다. 이 패스는 다른 패스와 완전히 같지는 않습니다. MIR 노드를 대상으로 하는 린트에는 고유한 실행 방법이 있습니다.

    • 예시: arithmetic_overflow 린트는 오버플로가 발생할 수 있는 상수 값을 감지하면 발생합니다.

대부분의 린트는 패스 시스템을 통해 잘 동작하며, 인터페이스가 꽤 간단하고 통합하기도 쉽습니다(대부분 특정 check 함수를 구현하기만 하면 됩니다). 하지만 일부 린트는 컴파일러 내 특정 코드 경로에 두면 더 작성하기 쉽습니다. 예를 들어, unused_mut 린트는 빌림 검사기의 일부 정보와 상태가 필요하므로 빌림 검사기 안에 구현되어 있습니다.

이러한 인라인 린트 중 일부는 린트 시스템이 준비되기 전에 발생합니다. 이러한 린트는 버퍼링되어, 린트 시스템이 준비되는 컴파일러의 이후 단계까지 보관됩니다. 컴파일러 초기 단계에서의 린트를 참조하십시오.

린트 정의 용어

린트는 LintStore를 통해 관리되며 다양한 방식으로 등록됩니다. 다음 용어들은 주로 등록 방식에 따라 구분되는 린트의 여러 종류를 가리킵니다.

  • 내장 린트는 컴파일러 소스 안에 정의되어 있습니다.
  • 드라이버 등록 린트는 외부 드라이버가 컴파일러 드라이버를 생성할 때 등록됩니다. 예를 들어, Clippy가 이 메커니즘을 사용합니다.
  • 도구 린트는 clippy::나 rustdoc:: 같은 경로 접두사가 붙은 린트입니다.
  • 내부 린트는 rustc:: 범위의 도구 린트로, rustc 소스 트리 자체에서만 실행되며 일반 내장 린트처럼 컴파일러 소스 안에 정의되어 있습니다.

린트 등록에 관한 자세한 내용은 LintStore 장에서 확인할 수 있습니다.

린트 선언하기

내장 컴파일러 린트는 rustc_lint 크레이트에 정의되어 있습니다. 다른 크레이트에서 구현해야 하는 린트는 rustc_lint_defs에 정의되어 있습니다. 가능하면 린트를 rustc_lint에 두는 것이 좋습니다. 이 크레이트는 의존성 루트에 가깝기 때문에 훨씬 빠르게 작업할 수 있다는 장점이 있습니다.

모든 린트는 LintPass trait를 구현하는 struct를 통해 구현됩니다(린트가 실행되기에 가장 적절한 시점에 따라 더 구체적인 린트 패스 트레이트인 EarlyLintPass나 LateLintPass 중 하나를 구현할 수도 있습니다). 이 트레이트 구현을 통해 린터가 AST를 순회하는 동안 특정 구문 구조를 검사할 수 있습니다. 그런 다음 컴파일 오류와 매우 비슷한 방식으로 린트를 내보낼 수 있습니다.

또한 declare_lint! 매크로를 통해 특정 린트의 메타데이터를 선언합니다. 이 매크로에는 이름, 기본 수준, 짧은 설명, 그리고 몇 가지 추가 세부 정보가 포함됩니다.

린트와 린트 패스는 반드시 컴파일러에 등록해야 한다는 점에 유의하십시오.

예를 들어, 다음 린트는 while true { ... }의 사용을 검사하고 대신 loop { ... }를 사용하도록 제안합니다.

// Declare a lint called `WHILE_TRUE`
declare_lint! {
    WHILE_TRUE,

    // warn-by-default
    Warn,

    // This string is the lint description
    "suggest using `loop { }` instead of `while true { }`"
}

// This declares a struct and a lint pass, providing a list of associated lints. The
// compiler currently doesn't use the associated lints directly (e.g., to not
// run the pass or otherwise check that the pass emits the appropriate set of
// lints). However, it's good to be accurate here as it's possible that we're
// going to register the lints via the get_lints method on our lint pass (that
// this macro generates).
declare_lint_pass!(WhileTrue => [WHILE_TRUE]);

// Helper function for `WhileTrue` lint.
// Traverse through any amount of parenthesis and return the first non-parens expression.
fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
    while let ast::ExprKind::Paren(sub) = &expr.kind {
        expr = sub;
    }
    expr
}

// `EarlyLintPass` has lots of methods. We only override the definition of
// `check_expr` for this lint because that's all we need, but you could
// override other methods for your own lint. See the rustc docs for a full
// list of methods.
impl EarlyLintPass for WhileTrue {
    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
        if let ast::ExprKind::While(cond, ..) = &e.kind
            && let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind
            && let ast::LitKind::Bool(true) = lit.kind
            && !lit.span.from_expansion()
        {
            let condition_span = cx.sess.source_map().guess_head_span(e.span);
            cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
                lint.build(fluent::example::use_loop)
                    .span_suggestion_short(
                        condition_span,
                        fluent::example::suggestion,
                        "loop".to_owned(),
                        Applicability::MachineApplicable,
                    )
                    .emit();
            })
        }
    }
}
example-use-loop = denote infinite loops with `loop {"{"} ... {"}"}`
  .suggestion = use `loop`

에디션에 따라 달라지는 린트

때로는 새 에디션에서 린트의 동작을 바꾸고 싶을 때가 있습니다. 이렇게 하려면 declare_lint! 호출에 전환 정보를 추가하기만 하면 됩니다:

declare_lint! {
    pub ANONYMOUS_PARAMETERS,
    Allow,
    "detects anonymous parameters",
    Edition::Edition2018 => Warn,
}

이렇게 하면 ANONYMOUS_PARAMETERS 린트가 2015 에디션에서는 기본적으로 허용(allow)되지만 2018 에디션에서는 기본적으로 경고(warn)를 냅니다.

자세한 내용은 에디션별 린트를 참고하십시오.

기능 게이트가 적용된 린트

특정 기능에 속한 린트는 크레이트에서 해당 기능이 활성화된 경우에만 사용할 수 있어야 합니다. 이를 지원하기 위해 린트 선언에 다음과 같이 기능 게이트를 포함할 수 있습니다:

declare_lint! {
    pub SOME_LINT_NAME,
    Warn,
    "a new and useful, but feature gated lint",
    @feature_gate = sym::feature_name;
}

향후 비호환 린트

컴파일러 내부에서 사용하는 future-incompatible이라는 용어는 rustc가 컴파일러 사용자에게 드러내는 의미보다 약간 더 넓은 의미를 가집니다.

rustc 내부에서 향후 비호환 린트는 사용자가 작성한 코드가 앞으로 컴파일되지 않을 수 있음을 사용자에게 알리기 위한 것입니다. 일반적으로 향후 비호환 코드가 생기는 이유는 두 가지입니다:

  • 사용자가 건전하지 않은(unsound) 코드를 작성했는데 컴파일러가 이를 실수로 받아들인 경우입니다. 건전성 구멍을 고치는 것(그 결과 사용자의 코드가 깨지는 것)은 Rust의 하위 호환성 보장 범위 안에 있지만, 이 린트는 코드가 어떤 에디션을 사용하든 상관없이 향후 rustc 버전에서 이런 일이 일어날 것임을 사용자에게 경고하기 위해 존재합니다. rustc가 사용자에게 “향후 비호환(future incompatible)“이라는 이름으로 드러내는 의미는 오직 이것뿐입니다.
  • 사용자가 작성한 코드가 다가올 에디션에서 컴파일러로 더 이상 컴파일되지 않거나 의미가 바뀌는 경우입니다. 이러한 린트는 흔히 “에디션 린트“라고 불리며, 일반적으로 사용자가 크레이트의 에디션을 업데이트하면 깨질 코드를 대상으로 린트를 수행하는 여러 “에디션 호환성” 린트 그룹(예: rust_2021_compatibility)에서 볼 수 있습니다. 자세한 내용은 마이그레이션 린트를 참고하십시오.

향후 비호환 린트는 @future_incompatible이라는 추가 “필드“와 함께 선언해야 합니다:

declare_lint! {
    pub ANONYMOUS_PARAMETERS,
    Allow,
    "detects anonymous parameters",
    @future_incompatible = FutureIncompatibleInfo {
        reason: fcw!(EditionError 2018 "slug-of-edition-guide-page")
    };
}

향후 호환되지 않게 될 변경이 왜 일어나는지를 설명하는 reason 필드에 주목하십시오. 이 필드는 사용자가 받는 진단 메시지를 바꿀 뿐만 아니라 해당 린트가 어떤 린트 그룹에 추가될지도 결정합니다. 위의 예시에서 이 린트는 “에디션 린트“이며(“reason“이 EditionError이므로), 익명 매개변수를 사용한 코드가 Rust 2018 이후로는 더 이상 컴파일되지 않는다는 것을 사용자에게 알려 줍니다.

LintStore::register_lints 내부에서 future_incompatible 필드가 있는 린트는 에디션 기반 린트 그룹(reason이 에디션과 연관된 경우)이나 future_incompatibility 린트 그룹 중 하나에 배치됩니다.

declare_lint! 매크로가 지원하지 않는 옵션 조합이 필요하다면 언제든지 declare_lint! 매크로를 수정하여 이를 지원하도록 할 수 있습니다.

린트 이름 변경 또는 제거

린트의 이름이 부적절하거나 린트가 더 이상 필요하지 않다고 판단되면 해당 린트를 이름 변경 또는 제거 대상으로 등록해야 하며, 이렇게 하면 사용자가 이전 린트 이름을 사용하려 할 때 경고가 발생합니다. 이름 변경이나 제거를 선언하려면 rustc_lint::register_builtins 함수의 코드에 store.register_renamed 또는 store.register_removed를 사용하는 줄을 추가하십시오.

store.register_renamed("single_use_lifetime", "single_use_lifetimes");

린트 그룹

린트는 그룹 단위로 활성화할 수 있습니다. 이러한 그룹은 rustc_lint::lib의 register_builtins 함수에 선언되어 있습니다. 새 그룹을 선언할 때는 add_lint_group! 매크로를 사용합니다.

예를 들면 다음과 같습니다.

add_lint_group!(sess,
    "nonstandard_style",
    NON_CAMEL_CASE_TYPES,
    NON_SNAKE_CASE,
    NON_UPPER_CASE_GLOBALS);

이 코드는 나열된 린트를 활성화하는 nonstandard_style 그룹을 정의합니다. 사용자는 소스 코드에 #![warn(nonstandard_style)] 속성을 작성하거나 명령줄에 -W nonstandard-style을 전달하여 이 린트들을 활성화할 수 있습니다.

일부 린트 그룹은 LintStore::register_lints에서 자동으로 생성됩니다. 예를 들어 FutureIncompatibleInfo와 함께 선언되고 그 이유가 FutureIncompatibilityReason::FutureReleaseError(declare_lint!에서 @future_incompatible을 사용할 때의 기본값)인 린트는 모두 future_incompatible 린트 그룹에 추가됩니다. 에디션에도 각자의 린트 그룹(예: rust_2021_compatibility)이 있으며, 이 그룹은 해당 에디션에서 깨지게 될 향후 비호환 코드를 알리는 모든 린트에 대해 자동으로 생성됩니다.

컴파일러 초기 단계에서의 린트

때로는 린트 시스템이 초기화되기 전(예: 파싱이나 매크로 확장 중)에 실행되는 린트를 정의해야 할 수도 있습니다. 경고를 낼지, 오류를 낼지, 아무것도 내지 않을지 알려면 린트 수준이 미리 계산되어 있어야 하므로 이는 문제가 됩니다.

이 문제를 해결하기 위해 린트 시스템이 처리될 때까지 린트를 버퍼에 담아 둡니다. Session과 ParseSess에는 모두 린트를 나중을 위해 버퍼에 담아 둘 수 있는 buffer_lint 메서드가 있습니다. 버퍼에 담긴 린트는 나중에 린트 시스템이 자동으로 처리합니다.

따라서 컴파일 초기에 실행되는 린트를 정의하려면 평소처럼 린트를 정의하되 buffer_lint로 린트를 호출합니다.

컴파일러의 더 이른 단계에서의 린트

파서(rustc_ast)는 다른 rustc* 크레이트 중 어느 것에도 의존성을 가질 수 없다는 점에서 흥미롭습니다. 특히 컴파일러의 모든 린트 인프라가 정의된 rustc_middle::lint나 rustc_lint에 의존할 수 없습니다. 이는 곤란한 일입니다!

이를 해결하기 위해 rustc_ast는 자체적인 버퍼 린트 타입을 정의하며, ParseSess::buffer_lint가 이 타입을 사용합니다. 매크로 확장이 끝나면 이렇게 버퍼에 담긴 린트는 컴파일러의 나머지 부분에서 사용하는 Session::buffered_lints로 옮겨집니다.

JSON 진단 출력

컴파일러는 진단 정보를 JSON 객체로 출력하는 --error-format json 플래그를 지원합니다(cargo fix 같은 도구에서 활용하기 위한 것입니다). 출력은 다음과 같습니다:

$ rustc json_error_demo.rs --error-format json
{"message":"cannot add `&str` to `{integer}`","code":{"code":"E0277","explanation":"\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n    fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n    foo.bar();\n}\n\nfn main() {\n    // we now call the method with the i32 type, which doesn't implement\n    // the Foo trait\n    some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n    fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n    foo.bar(); // we can now use this method since i32 implements the\n               // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n    fn bar(&self) {}\n}\n\nfn main() {\n    some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n    println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n                           //        implemented for the type `T`\n}\n\nfn main() {\n    // We now call the method with the i32 type,\n    // which *does* implement the Debug trait.\n    some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n    println!(\"{:?}\", foo);\n}\n\nfn main() {\n    // Calling the method is still fine, as i32 implements Debug.\n    some_func(5i32);\n\n    // This would fail to compile now:\n    // struct WithoutDebug;\n    // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"},"level":"error","spans":[{"file_name":"json_error_demo.rs","byte_start":50,"byte_end":51,"line_start":4,"line_end":4,"column_start":7,"column_end":8,"is_primary":true,"text":[{"text":"    a + b","highlight_start":7,"highlight_end":8}],"label":"no implementation for `{integer} + &str`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the trait `std::ops::Add<&str>` is not implemented for `{integer}`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error[E0277]: cannot add `&str` to `{integer}`\n --> json_error_demo.rs:4:7\n  |\n4 |     a + b\n  |       ^ no implementation for `{integer} + &str`\n  |\n  = help: the trait `std::ops::Add<&str>` is not implemented for `{integer}`\n\n"}
{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to previous error\n\n"}
{"message":"For more information about this error, try `rustc --explain E0277`.","code":null,"level":"","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0277`.\n"}

출력은 여러 줄로 이루어지며 각 줄은 하나의 JSON 객체입니다. 그러나 안타깝게도 이 줄들을 모두 합친 것은 유효한 JSON이 아니므로, 유효한 JSON이 필요한 도구나 기법(python3 -m json.tool로 파이프하기 등)을 사용할 수 없다는 점에 유의하십시오. (각 줄/객체를 플러시되는 즉시 보낼 수 있도록 LSP 성능을 위해 의도적으로 이렇게 만든 것이 아닐까 추측해 볼 수 있습니다.)

또한 “사람용” 출력을 문자열로 담고 있는 “rendered” 필드에도 유의하십시오. 이 필드는 UI 테스트가 모든 것을 두 번 컴파일하지 않고도 구조화된 JSON을 활용하는 동시에 “사람용” 출력(물론 색상은 제외하고)도 볼 수 있도록 도입되었습니다.

“사람“이 읽을 수 있는 형식의 이미터와 json 형식의 이미터는 rustc_errors 아래에 있으며, 둘 다 rustc_ast 크레이트에서 rustc_errors 크레이트로 옮겨졌습니다.

JSON 이미터는 JSON 직렬화를 위해 자체 Diagnostic 구조체(및 하위 구조체)를 정의합니다. 이것을 errors::Diag와 혼동하지 마십시오!

#[rustc_on_unimplemented]

이 속성을 사용하면 트레이트 정의에서 구현이 있어야 하는데 찾지 못했을 때의 오류 메시지를 수정할 수 있습니다. 속성 안의 문자열 리터럴은 형식 문자열이며, 이름 있는 매개변수로 형식을 지정할 수 있습니다. 어떤 매개변수를 쓸 수 있는지는 아래의 형식 지정 절을 참고하십시오.

#[rustc_on_unimplemented(message = "an iterator over \
    elements of type `{A}` cannot be built from a \
    collection of type `{Self}`")]
trait MyIterator<A> {
    fn next(&mut self) -> A;
}

fn iterate_chars<I: MyIterator<char>>(i: I) {
    // ...
}

fn main() {
    iterate_chars(&[1, 2, 3][..]);
}

사용자가 이 코드를 컴파일하면 다음과 같은 내용을 보게 됩니다;

error[E0277]: an iterator over elements of type `char` cannot be built from a collection of type `&[{integer}]`
  --> src/main.rs:13:19
   |
13 |     iterate_chars(&[1, 2, 3][..]);
   |     ------------- ^^^^^^^^^^^^^^ the trait `MyIterator<char>` is not implemented for `&[{integer}]`
   |     |
   |     required by a bound introduced by this call
   |
note: required by a bound in `iterate_chars`

다음 항목의 내용을 수정할 수 있습니다:

  • 주 오류 메시지(message)
  • 레이블(label)
  • 노트(note)

예를 들어, 다음 속성은

#[rustc_on_unimplemented(message = "message", label = "label", note = "note")]
trait MyIterator<A> {
    fn next(&mut self) -> A;
}

다음과 같은 출력을 생성합니다:

error[E0277]: message
  --> <file>:10:19
   |
10 |     iterate_chars(&[1, 2, 3][..]);
   |     ------------- ^^^^^^^^^^^^^^ label
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
   = note: note
note: required by a bound in `iterate_chars`

지금까지 설명한 기능은 #[diagnostic::on_unimplemented]로도 사용할 수 있습니다. 가능하다면 그것을 대신 사용하십시오.

필터링

더 상황에 맞는 오류 메시지를 제공할 수 있도록, on을 사용하여 이 필드들의 적용 여부를 필터링할 수 있습니다.

다음 불리언 플래그로 필터링할 수 있습니다:

  • crate_local: 트레이트 바운드를 충족하지 못하게 만든 코드가 사용자의 크레이트에 속하는지 여부입니다. 이 플래그는 의존성을 수정해야 하는 코드 변경을 제안하지 않도록 하는 데 사용됩니다.
  • direct: 파생된 의무가 아니라 사용자가 지정한 의무인지 여부입니다.
  • from_desugaring: 예를 들어 ?나 try 블록처럼 어떤 종류의 디슈가링 안에 있는지 여부입니다. 이 플래그로 매칭할 수도 있습니다. 아래를 참고하십시오.

name = "value" 형식을 사용하여 다음 이름과 값으로 매칭할 수 있습니다:

  • cause: ObligationCauseCode 열거형의 한 변형과 매칭합니다. "MainFunctionType"만 지원됩니다.
  • from_desugaring: DesugaringKind 열거형의 특정 변형과 매칭합니다. 디슈가링은 배리언트 이름으로 식별됩니다. 예를 들어 ? 디슈가링은 "QuestionMark", try 블록은 "TryBlock"입니다.
  • Self = "alloc::string::String"나 Rhs="i32"처럼 Self와 트레이트의 모든 제네릭 인자입니다.

컴파일러는 매칭에 사용할 여러 값을 제공할 수 있습니다. 예를 들면 다음과 같습니다:

  • 타입 인자를 해석한 형태와 해석하지 않은 형태로 보기 좋게 출력한 self_ty입니다.
  • self_ty가 타입이 알려진 정수 타입이면 "{integral}"입니다.
  • 해당하는 경우 "[]", "[{ty}]", "[{ty}; _]", "[{ty}; $N]"입니다.
  • 앞서 말한 슬라이스와 배열에 대한 참조입니다.
  • self가 함수이면 "fn", "unsafe fn" 또는 "#[target_feature] fn"입니다.
  • 타입이 숫자이지만 아직 추론되지 않았으면 "{integer}"와 "{float}"입니다.
  • self를 ADT로 매칭하기 위한 "{struct}", "{enum}", "{union}"입니다.
  • "[{integral}; _]"처럼 위 항목들을 조합한 것입니다.

예를 들어 Iterator 트레이트는 다음과 같이 필터링할 수 있습니다:

#[rustc_on_unimplemented(
    on(Self = "&str", note = "call `.chars()` or `.as_bytes()` on `{Self}`"),
    message = "`{Self}` is not an iterator",
    label = "`{Self}` is not an iterator",
    note = "maybe try calling `.iter()` or a similar method"
)]
pub trait Iterator {}

이는 다음과 같은 출력을 만들어 냅니다:

error[E0277]: `Foo` is not an iterator
 --> src/main.rs:4:16
  |
4 |     for foo in Foo {}
  |                ^^^ `Foo` is not an iterator
  |
  = note: maybe try calling `.iter()` or a similar method
  = help: the trait `std::iter::Iterator` is not implemented for `Foo`
  = note: required by `std::iter::IntoIterator::into_iter`

error[E0277]: `&str` is not an iterator
 --> src/main.rs:5:16
  |
5 |     for foo in "" {}
  |                ^^ `&str` is not an iterator
  |
  = note: call `.chars()` or `.bytes() on `&str`
  = help: the trait `std::iter::Iterator` is not implemented for `&str`
  = note: required by `std::iter::IntoIterator::into_iter`

on 필터는 cfg 속성과 비슷하게 all, any, not 술어를 받습니다:

#[rustc_on_unimplemented(on(
    all(Self = "&str", T = "alloc::string::String"),
    note = "you can coerce a `{T}` into a `{Self}` by writing `&*variable`"
))]
pub trait From<T>: Sized {
    /* ... */
}

포매팅

문자열 리터럴은 중괄호로 감싼 매개변수를 받는 형식 문자열이지만, 위치 매개변수와 나열된 매개변수는 허용되지 않습니다. 사용할 수 있는 매개변수 이름은 다음과 같습니다:

  • Self와 트레이트의 모든 제네릭 매개변수입니다.
  • This: 속성이 붙은 트레이트의 이름이며, 제네릭은 포함하지 않습니다.
  • This:path: 속성이 붙은 트레이트의 전체 경로이며, 제네릭은 해석하지 않은 상태입니다.
  • This:resolved: 속성이 붙은 트레이트의 전체 경로이며, 제네릭은 해석된 상태입니다. 또한 이 매개변수는 Fn(...) 트레이트를 “슈거” 형태로 표시합니다.
  • ItemContext: 현재 위치한 hir::Node의 종류로, "an async block", "a function", "an async function" 등이 있습니다.

예를 들어 다음과 같은 코드는:

#![feature(rustc_attrs)]

#[rustc_on_unimplemented(message = "Self = `{Self}`, \
    T = `{T}`, this = `{This}`, trait = `{Trait}`, \
    context = `{ItemContext}`")]
pub trait From<T>: Sized {
    fn from(x: T) -> Self;
}

fn main() {
    let x: i8 = From::from(42_i32);
}

메시지를 다음과 같이 포매팅합니다.

"Self = `i8`, T = `i32`, this = `From`, trait = `From<i32>`, context = `a function`"

  1. 이 경험 법칙은 @estebank가 여기에서 제안했습니다. ↩