Rust의 attribute 종류 및 의미

#[must_use]

Rust에서의 attribute(속성)는 컴파일러에게 특정 코드에 대한 추가적인 정보를 제공하여, 코드의 컴파일 방식 또는 동작 방식을 제어하거나, 경고/에러 메시지를 제어하는 데 사용됩니다. derive, allow, warn, cfg, test 등 다양한 종류가 있습니다.

1. attribute의 적용 범위별 종류

Rust의 attribute는 크게 다음과 같은 종류로 나눌 수 있습니다:

종류형태 예시설명
Item attribute#[derive(Debug)]함수, 구조체 등 개별 항목에 적용
Crate attribute#![allow(dead_code)]크레이트 전체에 적용, 보통 파일 상단에 위치
Inner attribute#![cfg(test)]모듈 또는 크레이트 내부에 선언, 내부 항목에 영향을 줌
Outer attribute#[test]특정 항목(함수 등)에만 적용

2. 주요 attribute

가. 컴파일러 관련 attribute

Attribute설명예시
#[allow(…)]특정 경고를 무시#[allow(dead_code)]
#[warn(…)]경고를 표시 (기본값)#[warn(unused_variables)]
#[deny(…)]해당 사항이 있으면 컴파일 에러#[deny(missing_docs)]
#[forbid(…)]deny보다 강하게 재정의 불가#[forbid(unsafe_code)]

나. 파생(derive) 관련 attribute

Rust의 많은 기능은 trait을 자동으로 구현해주는 #[derive(…)]를 통해 사용합니다.

#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}

Debug, Clone, PartialEq 트레이트를 자동 구현합니다.

다. 조건부 컴파일 관련 attribute

Attribute설명예시
#[cfg(…)]조건에 따라 컴파일 여부 결정#[cfg(target_os = “windows”)]
#[cfg_attr(…)]조건이 참일 때에만 다른 attribute 적용#[cfg_attr(test, derive(Debug))]

[cfg_attr(test, derive(Debug))]는 테스트를 실행할 때만 Debug 트레이트를 자동 구현합니다.

라. 테스트/벤치마크 관련 attribute

Attribute설명예시
#[test]단위 테스트 함수임을 표시#[test]
fn test_add() { … }
#[bench]벤치마크 함수 표시 (불안정)#[bench]
fn bench_sort(…)
#[ignore]테스트 실행 시 무시됨#[test] #[ignore]
fn test_heavy() {}

마. 매크로 관련 attribute

Attribute설명예시
#[macro_use]외부 크레이트의 매크로를 현재 스코프로 불러옴 (Rust 2015 방식)#[macro_use]
extern crate log;
#[proc_macro]프로시저 매크로 정의프로시저 매크로 crate에서 사용됨

바. 기타 유용한 attribute

Attribute설명예시
#[inline], #[inline(always)]함수 인라인화 힌트#[inline(always)]
fn fast_fn() {}
#[repr(C)]구조체 메모리 레이아웃 C와 호환#[repr(C)]
struct MyStruct { … }
#[non_exhaustive]미래 확장을 위해 열거형 등의 exhaustive 검사 방지#[non_exhaustive]
enum MyEnum { … }
#[must_use]반환값을 사용하지 않으면 경고#[must_use]
fn compute() -> i32 { … }

※ 함수를 호출할 때 일반적으로는 함수 본문은 어딘가에 있고 호출하면 그 함수로 점프(jump)해서 실행한 뒤 다시 돌아옵니다. 하지만 인라인화는 이런 점프 없이 함수의 코드를 호출한 곳에 ‘복붙’하듯 직접 삽입하는 방식입니다.

사. #![…]과 #[…] 차이

구분형태설명
Inner attribute#![…]모듈, 크레이트 전체에 적용 (파일 상단에 위치)
Outer attribute#[…]특정 항목(함수, struct 등)에 적용

3. attribute 예제

가. #[derive(…)] — 트레이트 자동 구현

(1) 설명: 구조체(struct)나 열거형(enum)에 대해, 특정 트레이트의 기본 구현을 자동 생성합니다.

(2) 자주 사용되는 트레이트:

  • Debug : {:?} 포맷으로 출력 가능
  • Clone, Copy : 값 복사 가능
  • PartialEq, Eq : 값 비교 가능
  • PartialOrd, Ord : 정렬 가능
  • Hash : 해시 사용 가능

(3) 예제:

#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}

fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone(); // Clone 사용
println!("{:?}", p2); // Debug 사용
println!("p1 == p2? {}", p1 == p2); // PartialEq 사용
}

나. #[allow(…)], #[warn(…)], #[deny(…)], #[forbid(…)]

(1) 설명: 컴파일 경고/에러를 제어합니다. => 위 2.의 가. 컴파일러 관련 attribute 설명 참고

(2) 주요 옵션:

  • dead_code : 사용되지 않는 코드
  • unused_variables : 사용되지 않는 변수
  • non_snake_case : snake_case 규칙 위반

(3) 예제:

#[allow(dead_code)]
fn unused() {
println!("사용되지 않지만 경고 없음");
}

#[deny(unused_variables)]
fn foo() {
let x = 5; // 컴파일 에러 발생: 변수 사용 안 함
}

다. #[test], #[ignore] — 테스트 함수 지정

(1) 설명: 테스트 프레임워크를 위한 attribute입니다.

(2) 예제:

#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn heavy_test() {
// 실행하려면 `cargo test -- --ignored`
}
}

라. #[cfg(…)] — 조건부 컴파일

(1) 설명: 플랫폼, 기능 등에 따라 코드 포함 여부 결정

(2) 예제:

#[cfg(target_os = "windows")]
fn run_on_windows() {
println!("Windows 전용 코드");
}

#[cfg(not(target_os = "windows"))]
fn run_on_unix() {
println!("Unix 계열 전용 코드");
}

(3) Cargo.toml에서 feature와 연계:

[features]
my_feature = []
#[cfg(feature = "my_feature")]
fn my_func() {
println!("my_feature가 활성화됨");
}

“my_feature”라는 기능(feature)이 활성화됐을 때만 my_func() 함수의 정의 자체를 컴파일에 포함시키고, 아니면 아예 없는 코드처럼 무시해버립니다.

마. #[inline] — 인라인 최적화 힌트

(1) 설명: 컴파일러에게 해당 함수를 인라인하도록 유도

(2) 예제:

#[inline(always)]
fn fast_add(a: i32, b: i32) -> i32 {
a + b
}

fn main() {
let x = add(3, 4);
}
  • 일반 호출: main에서 add 함수로 점프하고, 결과를 받아와서 x에 저장
  • 인라인: 컴파일할 때 add(3, 4)를 그냥 3 + 4로 바꿔서 main 함수 안에 넣어버림

바. #[repr(…)] — 메모리 레이아웃 제어

(1) 설명: 구조체/열거형의 메모리 정렬 방법을 지정(repr은 representation(표현)의 약어)

(2) 종류:

  • C: C 언어와 동일한 레이아웃
  • packed: 패딩 없이 압축
  • transparent: 단일 필드 감싸기

(3) 예제:

#[repr(C)]
struct MyStruct {
a: u8,
b: u32,
}

사. #[non_exhaustive] — 미래 확장을 위한 열거형

(1) 설명: 나중에 항목이 더 생길 수 있으니, “지금 있는 것만 가지고 match를 완벽하게 쓰지 마세요“라는 의미입니다.

(2) 예제:

#[non_exhaustive]
pub enum Error {
Io,
Parse,
}

이 코드는 다음과 같은 의미를 가집니다.

Error enum은 지금은 Io와 Parse 두 가지 variant만 있지만, 앞으로 새로운 variant가 추가될 수 있으므로 다른 크레이트에서는 이 enum을 match할 때 지금 있는 것만으로 열거하지 못하게 제한하는 것입니다.

그래서 #[non_exhaustive]를 붙이면, 사용자는 반드시 _를 이용한 default arm을 추가해야만 컴파일이 가능하며, 이것은 다른 크레이트가 match를 지금 있는 것만으로 exhaustively(완전하게) 작성하면, 나중에 enum에 새로운 variant를 추가했을 때 컴파일이 깨질 수 있기 때문입니다.

아. #[must_use] — 반환값 사용하지 않으면 경고

(1) 설명: 실수로 무시하면 안 되는 반환값을 경고로 알려줌

(2) 예제:

#[must_use]
fn important_result() -> Result<(), String> {
Err("Error".into())
}

fn main() {
important_result(); // 경고 발생!
}

위 코드는 important_result 함수의 반환 값을 사용하지 않아 warning(경고)이 발생하므로 아래와 같은 식으로 수정해야 합니다.

fn main() {
    let result = important_result(); // OK
    if let Err(e) = result {
        println!("에러 발생: {}", e);
    }
}

자. #[macro_use], #[macro_export] — 매크로 관련

(1) 설명: 외부 crate의 매크로를 가져오거나 내보낼 때 사용

(2) 예제:

// 외부 매크로 사용
#[macro_use]
extern crate log;

// 매크로 정의 및 export
#[macro_export]
macro_rules! hello {
() => {
println!("Hello, macro!");
};
}
  • #[macro_use] : 외부 크레이트 log에서 정의된 매크로(log!, info!, warn! 등)들을 이 파일 안에서 직접 사용할 수 있도록 가져오라는 의미로서, 예전 Rust 스타일 (Rust 2018 이전)의 방식이며, 최신 Rust (2018 edition 이후)에서는 use log::info; 식으로 가져오는 게 일반적입니다.
  • #[macro_export] : 이 매크로를 다른 모듈/크레이트에서 사용할 수 있게 공개(export) 하겠다는 의미입니다. 따라서, 이 매크로를 crate_name::hello!() 식으로 다른 crate에서도 쓸 수 있습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다