속성

속성 (Attributes)

속성은 Rust가 컴파일러와 라이브러리 코드에 메타데이터를 전달하는 핵심 수단이에요. 함수 하나에 테스트라는 표시를 붙이거나, 특정 타깃에서만 모듈을 컴파일하도록 조건을 거는 일이 모두 속성으로 이뤄지죠. 이번 장에서는 속성의 문법, 종류, 그리고 실제로 어떤 속성들이 있는지 훑어볼게요.

출처: Rust Reference

본문

속성은 이름과 규약, 언어, 컴파일러 버전에 따라 해석되는 범용적이고 자유로운 형식의 메타데이터예요. ECMA-335에 정의된 Attribute를 모델로 하고 있고, 문법은 ECMA-334(C#)에서 가져왔어요.

구문

InnerAttribute → # ! [ Attr ]
OuterAttribute → # [ Attr ]
Attr →      SimplePath AttrInput?
    | unsafe ( SimplePath AttrInput? )
AttrInput →      DelimTokenTree
    | = Expression

내부 속성 (Inner attribute)

해시(#) 뒤에 느낌표(!)가 붙는 형태를 내부 속성이라고 해요. 이 속성은 그 속성이 선언된 형태(form) 자체에 적용돼요. 예를 들어 #![crate_type = "lib"]처럼 크레이트나 모듈에 붙이면 그 전체에 메타데이터가 적용되죠.

#![allow(unused)]
fn main() {
// General metadata applied to the enclosing module or crate.
#![crate_type = "lib"]

// Inner attribute applies to the entire function.
fn some_unused_variables() {
  #![allow(unused_variables)]

  let x = ();
  let y = ();
  let z = ();
}
}

외부 속성 (Outer attribute)

느낌표 없이 해시 뒤바로 [ ... ]가 오는 형태는 외부 속성이에요. 이 속성은 바로 뒤에 따라오는 형태에 적용돼요. #[test]처럼 함수에, #[cfg(...)]처럼 모듈에 적용하는 게 전형적인 예시예요.

#![allow(unused)]
fn main() {
// A function marked as a unit test
#[test]
fn test_foo() {
    /* ... */
}

// A conditionally-compiled module
#[cfg(target_os = "linux")]
mod bar {
    /* ... */
}

// A lint attribute used to suppress a warning/error
#[allow(non_camel_case_types)]
type int8_t = i8;
}

속성은 속성의 경로(path), 그 뒤에 오는 선택적인 delimiter 토큰 트리로 구성돼요. 이 토큰 트리의 해석은 속성마다 정의돼요. 매크로 속성이 아닌 속성들은 입력으로 등호(=) 뒤에 표현식이 오는 형태도 허용해요. 자세한 내용은 아래의 meta item 구문을 참고하면 돼요.

unsafe 속성

어떤 속성은 적용 자체가 unsafe할 수 있어요. 이런 속성을 쓸 때는 특정 의무(obligation)를 지켜야 하고, 컴파일러가 이를 검사할 수 없기 때문에 개발자가 직접 보증해야 해요. 그 보증을 표시하기 위해 속성을 unsafe(..)로 감싸요. 예를 들면 #[unsafe(no_mangle)]처럼요.

unsafe한 속성은 다음과 같아요:

  • export_name
  • link_section
  • naked
  • no_mangle

속성의 분류

속성은 다음 네 가지로 분류돼요:

  • 내장 속성 (Built-in attributes)
  • 프로시저 매크로 속성 (Proc macro attributes)
  • derive 매크로 도우미 속성 (Derive macro helper attributes)
  • 도구 속성 (Tool attributes)

속성은 언어의 여러 형태에 적용될 수 있어요:

  • 모든 항목(item) 선언은 외부 속성을 받을 수 있고, extern 블록, 함수, 구현(impl), 모듈은 내부 속성도 받을 수 있어요.
  • 대부분의 문장(statement)은 외부 속성을 받을 수 있어요. (표현식 문장에 대한 제한은 표현식 속성 참고)
  • 블록 표현식은 외부·내부 속성 둘 다 받을 수 있는데, 표현식 문장의 외부 표현식이거나 다른 블록 표현식의 마지막 표현식일 때만 가능해요.
  • enum 변형(variant)과 struct·union 필드는 외부 속성을 받아요.
  • match 표현식의 arm은 외부 속성을 받아요.
  • 제네릭 라이프타임·타입 매개변수는 외부 속성을 받아요.
  • 표현식은 제한된 상황에서만 외부 속성을 받아요. 자세한 내용은 표현식 속성 참고.
  • 함수·클로저·함수 포인터의 매개변수는 외부 속성을 받아요. 함수 포인터와 extern 블록에서 ...로 표기하는 가변 인자(variadic) 매개변수에 붙는 속성도 여기에 포함돼요.
  • 인라인 어셈블리 템플릿 문자열과 피연산자는 외부 속성을 받아요. 의미론적으로 허용되는 속성은 일부뿐이니 자세한 내용은 asm의 supported-attributes를 참고해요.

Meta item 속성 구문

대부분의 내장 속성이 Attr 규칙에 사용하는 문법을 meta item이라고 불러요. 문법은 다음과 같아요:

MetaItem →      SimplePath
    | SimplePath = Expression
    | SimplePath ( MetaSeq? )

MetaSeq →    MetaItemInner ( , MetaItemInner )* ,?

MetaItemInner →      MetaItem
    | Expression

meta item 안의 표현식은 매크로 확장 결과가 리터럴 표현식이어야 해요. 이때 정수·실수 타입 접미사는 포함하면 안 돼요. 리터럴이 아닌 표현식은 구문상으로는 받아들여지고(프로시저 매크로에 넘겨질 수 있고) 파싱 후에 거부돼요.

한 가지 주의할 점은, 속성이 다른 매크로 안에 나타나면 바깥 매크로가 먼저 확장된 뒤에 속성이 확장된다는 거예요. 예를 들어 아래 코드는 Serialize 프로시저 매크로가 먼저 확장되는데, 그 안의 include_str! 호출이 나중에 확장될 수 있도록 보존해 줘야 해요:

#[derive(Serialize)]
struct Foo {
    #[doc = include_str!("x.md")]
    x: u32
}

또한 속성 안의 매크로는 그 항목에 적용된 다른 모든 속성이 확장된 뒤에야 확장돼요:

#[macro_attr1] // expanded first
#[doc = mac!()] // `mac!` is expanded fourth.
#[macro_attr2] // expanded second
#[derive(MacroDerive1, MacroDerive2)] // expanded third
fn foo() {}

여러 내장 속성은 입력을 지정할 때 meta item 구문의 서로 다른 부분집합을 사용해요. 자주 쓰이는 형태의 문법 규칙은 다음과 같아요:

MetaWord →    IDENTIFIER

MetaNameValueStr →    IDENTIFIER = ( STRING_LITERAL | RAW_STRING_LITERAL )

MetaListPaths →    IDENTIFIER ( ( SimplePath ( , SimplePath )* ,? )? )

MetaListIdents →    IDENTIFIER ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )

MetaListNameValueStr →    IDENTIFIER ( ( MetaNameValueStr ( , MetaNameValueStr )* ,? )? )

활성 속성과 비활성 속성 (Active and inert attributes)

속성은 활성(active) 이거나 비활성(inert) 이에요. 속성 처리 과정에서 활성 속성은 자신이 붙어 있던 형태에서 스스로를 제거하고, 비활성 속성은 그 자리에 남아요.

cfgcfg_attr 속성이 활성 속성이고, 속성 매크로(attribute macro)도 활성 속성이에요. 그 외의 모든 속성은 비활성이에요.

도구 속성 (Tool attributes)

컴파일러는 외부 도구를 위한 속성을 허용할 수 있어요. 각 도구는 tool prelude 안에 자신만의 모듈을 가져요. 속성 경로의 첫 번째 세그먼트가 도구의 이름이고, 그 뒤에 오는 세그먼트들의 해석은 도구마다 달라요.

도구를 사용하지 않을 때는 그 도구의 속성이 경고 없이 받아들여져요. 도구를 사용할 때는 도구가 자신의 속성 처리를 담당해요.

no_implicit_prelude 속성을 쓰면 도구 속성은 사용할 수 없어요.

#![allow(unused)]
fn main() {
// Tells the rustfmt tool to not format the following element.
#[rustfmt::skip]
struct S {
}

// Controls the "cyclomatic complexity" threshold for the clippy tool.
#[clippy::cyclomatic_complexity = "100"]
pub fn f() {}
}

참고: rustc는 현재 "clippy", "rustfmt", "diagnostic", "miri", "rust_analyzer" 이 다섯 도구를 인식해요.

내장 속성 인덱스

다음은 모든 내장 속성의 목록이에요.

조건부 컴파일 (Conditional compilation)

  • cfg — 조건부 컴파일을 제어해요.
  • cfg_attr — 조건에 따라 속성을 포함해요.

테스트 (Testing)

  • test — 함수를 테스트로 표시해요.
  • ignore — 테스트 함수를 비활성화해요.
  • should_panic — 테스트가 패닉을 일으켜야 함을 나타내요.

Derive

  • derive — 자동 trait 구현을 만들어요.
  • automatically_derived — derive가 만든 구현을 표시하는 마커예요.

매크로 (Macros)

  • macro_exportmacro_rules 매크로를 크레이트 간 사용을 위해 내보내요.
  • macro_use — 매크로의 가시성을 넓히거나 다른 크레이트에서 매크로를 가져와요.
  • proc_macro — 함수형 매크로를 정의해요.
  • proc_macro_derive — derive 매크로를 정의해요.
  • proc_macro_attribute — 속성 매크로를 정의해요.

진단 (Diagnostics)

  • allow, expect, warn, deny, forbid — 기본 lint 수준을 바꿔요.
  • deprecated — deprecation 경고를 생성해요.
  • must_use — 사용되지 않는 값에 대한 lint를 만들어요.
  • diagnostic::on_unimplemented — trait이 구현되지 않았을 때 컴파일러가 특정 오류 메시지를 내도록 힌트를 줘요.
  • diagnostic::do_not_recommend — 오류 메시지에서 특정 trait 구현을 보여주지 않도록 힌트를 줘요.

ABI, 링크, 심볼, FFI

  • link — extern 블록과 연결할 네이티브 라이브러리를 지정해요.
  • link_name — extern 블록 안의 함수·static의 심볼 이름을 지정해요.
  • link_ordinal — extern 블록 안의 함수·static의 심볼 오디널(ordinal)을 지정해요.
  • no_link — extern 크레이트의 링크를 막아요.
  • repr — 타입 레이아웃을 제어해요.
  • crate_type — 크레이트의 종류(라이브러리, 실행 파일 등)를 지정해요.
  • no_mainmain 심볼을 내보내지 않도록 해요.
  • export_name — 함수·static의 내보낼 심볼 이름을 지정해요.
  • link_section — 함수·static에 사용할 오브젝트 파일의 섹션을 지정해요.
  • no_mangle — 심볼 이름 인코딩을 비활성화해요.
  • used — 출력 오브젝트 파일에 static 항목을 유지하도록 컴파일러에 강제해요.
  • crate_name — 크레이트 이름을 지정해요.

코드 생성 (Code generation)

  • inline — 코드를 인라인하라는 힌트예요.
  • cold — 함수가 호출될 가능성이 낮다는 힌트예요.
  • naked — 함수의 프롤로그와 에필로그 생성을 막아요.
  • no_builtins — 특정 내장 함수의 사용을 비활성화해요.
  • target_feature — 플랫폼별 코드 생성을 구성해요.
  • track_caller — 부모 호출 위치를 std::panic::Location::caller()에 전달해요.
  • instruction_set — 함수 코드 생성에 사용할 명령어 집합을 지정해요.

문서 (Documentation)

  • doc — 문서를 지정해요. 자세한 내용은 The Rustdoc Book을 참고해요. 문서 주석(doc comment)은 doc 속성으로 변환돼요.

프렐루드 (Preludes)

  • no_std — prelude에서 std를 제거해요.
  • no_implicit_prelude — 모듈 안에서 prelude 조회를 비활성화해요.

모듈 (Modules)

  • path — 모듈의 파일 이름을 지정해요.

제한 (Limits)

  • recursion_limit — 특정 컴파일 타임 연산의 최대 재귀 한도를 설정해요.
  • type_length_limit — 다형적 타입의 최대 크기를 설정해요.

런타임 (Runtime)

  • panic_handler — 패닉을 처리할 함수를 설정해요.
  • global_allocator — 전역 메모리 할당자를 설정해요.
  • windows_subsystem — 연결할 windows 서브시스템을 지정해요.

기능 (Features)

  • feature — 불안정하거나 실험적인 컴파일러 기능을 활성화할 때 사용해요. rustc에 구현된 기능 목록은 The Unstable Book을 참고해요.

타입 시스템 (Type System)

  • non_exhaustive — 타입에 앞으로 필드나 변형이 더 추가될 것임을 나타내요.

디버거 (Debugger)

  • debugger_visualizer — 타입의 디버거 출력을 지정하는 파일을 포함해요.
  • collapse_debuginfo — 매크로 호출이 debuginfo에 인코딩되는 방식을 제어해요.

더 알아보기 (Learn more)

  • 내장 속성 각각의 상세 규칙은 The Rust Reference의 각 항목과 The Rustdoc Book, The Unstable Book에서 다뤄요.
  • 조건부 컴파일을 실무에서 쓸 때는 cfg 속성과 Rust의 cfg! 매크로를 함께 공부하면 좋아요.