rokdocs
v0.1

6.3. #[cfg] 조건부 컴파일

#[cfg(...)] 속성은 컴파일 타임 조건에 따라 항목을 빌드에 포함하거나 제외하는 데 쓰입니다. 문법은 다음과 같이 정의됩니다.

cfg_predicate   ::= identifier                              // flag
                  | identifier "=" string_lit               // key=value
                  | "all" "(" pred_list ")"
                  | "any" "(" pred_list ")"
                  | "not" "(" cfg_predicate ")"
pred_list       ::= cfg_predicate ("," cfg_predicate)* ","?
cfg_attr        ::= "#" "[" "cfg" "(" cfg_predicate ")" "]"
cfg_attr_attr   ::= "#" "[" "cfg_attr" "(" cfg_predicate ("," attr_body)+ ")" "]"
cfg_macro       ::= "cfg" "!" "(" cfg_predicate ")"

6.3.1. 기본 형태

가장 단순한 사용 예는 다음과 같습니다.

#[cfg(target_os = "linux")]
fn linux_specific() { ... }

#[cfg(target_arch = "x86_64")]
const HAS_AVX: bool = true;

#[cfg(test)]
mod tests { ... }

#[cfg(...)] 는 인자로 predicate 를 받으며, 조건이 참으로 평가되면 그 항목이 컴파일 대상에 포함되고, 거짓이면 제외됩니다. 제외된 항목은 처음부터 적히지 않은 것처럼 동작하기 때문에, 다른 코드의 이름 해소 과정에서도 보이지 않습니다.

6.3.2. predicate 종류

key = value

키와 값을 짝지어 조건을 표현하는 형태입니다.

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
#[cfg(target_pointer_width = "64")]
#[cfg(feature = "gpu")]

표준 키는 다음과 같이 정해져 있습니다.

키가능한 값
target_arch"x86_64", "aarch64", "x86", "arm", "wasm32"
target_os"linux", "macos", "windows", "wasi", "none"
target_pointer_width"64", "32"
target_endian"little", "big"
target_family"unix", "windows", "wasm"
target_env"gnu", "musl", "msvc"
feature사용자 정의. CLI 또는 패키지 매니저에서 설정합니다

타깃에 없으면 target_family / target_env 키는 없습니다. Tier 1 밖의 타깃은 target_arch 값 "riscv64", "riscv32", "powerpc64", "s390x" 도 줍니다.

target_* 키는 모두 빌드의 --target (9.1.2) 을 따릅니다. --target 이 없으면 호스트 타깃입니다.

단순 플래그

값 없이 키만 적는 부울 플래그 형태도 있습니다.

#[cfg(test)]                 // 테스트 빌드에서만
#[cfg(debug_assertions)]      // 디버그 빌드에서만

결합자

여러 조건을 조합하고 싶다면 결합자를 사용하면 됩니다.

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(not(target_os = "windows"))]

각 결합자의 의미는 다음 표와 같습니다.

결합자의미
all(...)모든 인자가 참
any(...)하나라도 참
not(...)인자가 거짓

결합자는 자유롭게 중첩할 수 있습니다.

#[cfg(all(
    target_os = "linux",
    any(target_arch = "x86_64", target_arch = "aarch64"),
    not(feature = "minimal"),
))]
fn linux_native() { ... }

6.3.3. 부착 위치

#[cfg] 는 항목, 필드 (이름 있는 필드, tuple, union, 변형, tuple 변형의 필드), enum 변형, 트레잇 / impl / extern 블록의 항목, 문, match arm, 구조체 리터럴 필드, 함수 매개변수에 적용됩니다. cfg 가 거짓인 매개변수는 시그니처에서 빠지며, 호출은 남은 매개변수의 인자를 넘깁니다. 클로저 매개변수에는 속성을 붙일 수 없습니다.

#[cfg(...)] fn f() { ... }
#[cfg(...)] struct S { ... }
#[cfg(...)] mod m { ... }
#[cfg(...)] use foo::bar;
#[cfg(...)] static X: i32 = 0;

struct Foo {
    #[cfg(target_pointer_width = "64")]
    big_field: u64;                              // 64-bit 에서만 존재
}

fn run(op: Op) {
    #[cfg(test)] check();                        // 문
    match op {
        Op::Add => add(),
        #[cfg(debug_assertions)]
        Op::Trace => trace(),                    // match arm
        _ => {},
    }
}

조건이 거짓이면 그 항목은 없는 것처럼 처리됩니다: 파싱 직후, derive 와 다른 속성이 보기 전에 제거됩니다 (6.3.7). 메타 블록 확장의 결과도 빌드의 cfg 로 파싱하므로, 그 안의 #[cfg] 도 같은 방식으로 적용됩니다.

6.3.4. #[cfg_attr(...)] 조건부 다른 속성

#[cfg_attr] 은 조건부로 다른 속성을 부착하고 싶을 때 사용합니다.

#[cfg_attr(target_os = "windows", link("kernel32"))]
extern "C" { ... }

#[cfg_attr(test, derive(Debug))]
struct S { ... }

형태는 cfg_attr(조건, 적용할_속성) 이며, 조건이 참이면 뒤의 속성이 마치 직접 부착된 것처럼 동작합니다. 첫 예시에서는 Windows 빌드에서만 #[link("kernel32")] 가 적용되고, 두 번째 예시에서는 테스트 빌드에서만 #[derive(Debug)] 가 적용되는 식입니다.

여러 속성을 한 번에 적용할 수도 있습니다.

#[cfg_attr(test, derive(Debug), allow(unused))]
struct S { ... }

6.3.5. cfg!(...) 컴파일 타임 bool 값

cfg!(...) 는 식 자리에 등장해서 컴파일 타임에 bool 값으로 평가되는 형태입니다.

const IS_LINUX: bool = cfg!(target_os = "linux");

if cfg!(feature = "gpu") {
    use_gpu()
} else {
    use_cpu()
}

#[cfg] 와 동일한 predicate 를 인자로 받으며, 결과는 const 의 const 표현식이라서 다른 const 자리에도 그대로 사용할 수 있습니다. cfg! 는 include_str! / include_bytes! (6.11) 처럼 컴파일러 내장 형태이지 매크로 (호출로 트리거되는 메타 정의, 6.8.12) 가 아니라는 점도 알아두는 것이 좋습니다. 컴파일러에 고정되어 재정의·가림이 불가하고 이름이 예약됩니다.

#[cfg] 와 cfg!() 의 차이는 다음과 같습니다.

  • #[cfg]: 항목 전체를 빌드에 포함하거나 제외합니다.
  • cfg!(): 런타임 분기처럼 보이지만 실제로는 컴파일 타임 상수이므로, 한쪽 분기는 dead-code-elimination 으로 제거됩니다.
// #[cfg] - 두 함수 중 하나만 컴파일됨
#[cfg(feature = "gpu")] fn run() { use_gpu() }
#[cfg(not(feature = "gpu"))] fn run() { use_cpu() }

// cfg!() - 한 함수 안에서 분기, 컴파일러가 한쪽 제거
fn run() {
    if cfg!(feature = "gpu") { use_gpu() } else { use_cpu() }
}

두 형태는 사용 편의성과 코드 구조에 따라 골라 쓰면 됩니다.

6.3.6. 매개변수의 출처

target_* 키

target_* 키들은 빌드의 대상 플랫폼에 따라 자동으로 설정됩니다. 플랫폼 타깃 의 15 개 Tier 1 타깃에 대해 명확한 값을 갖습니다.

feature 키

feature 키는 CLI 옵션 또는 패키지 매니저와 빌드 도구가 설정합니다.

rokc --cfg feature=gpu

패키지 매니저 rok 이 패키지마다 이를 정합니다: 각 패키지는 빌드가 그 패키지에 켠 feature 에 대해서만 정확히 feature = "<f>" 로 컴파일됩니다 (9.10.4c). rokc --cfg feature=<f> 는 컴파일 중인 패키지의 feature 만 정하며, 의존 패키지의 feature 는 --extern-feature <name>=<f> 로 줍니다 (9.1). 표준 라이브러리에는 feature 가 없습니다.

test / debug_assertions

이 두 플래그는 빌드 모드에 따라 결정됩니다.

  • test: rokc test 또는 동등한 명령으로 빌드할 때 참이 됩니다.
  • debug_assertions: -O0 일 때 참이 됩니다.

6.3.7. 평가 시점

#[cfg] 의 평가는 parse 직후에 일어납니다. 거짓 조건의 항목은 AST 에서 즉시 제거되고, 후속 패스 (이름 해소, 타입 검사, 코드 생성) 는 남은 AST 만 보게 됩니다.

이 시점은 메타 확장 패스 (템플릿 토큰과 확장) 보다 앞서기 때문에, cfg 가 거짓인 항목에 부착된 사용자 정의 derive 와 attribute 도 처리되지 않습니다.

6.3.8. 부정/충돌하는 cfg

같은 빌드에서 조건이 동시에 참이 되어 두 정의가 살아남으면, 일반적인 중복 정의 에러가 발생합니다.

#[cfg(target_os = "linux")]
fn impl_a() { ... }

#[cfg(target_os = "linux")]    // 같은 cfg + 같은 이름 → 충돌
fn impl_a() { ... }            // ERROR - 두 정의가 같은 빌드에 동시에 존재

반면 다음 예시는 문제가 없습니다. 두 cfg 가 상호 배타적이라서 어떤 빌드에서도 한 정의만 활성화되기 때문입니다.

#[cfg(target_os = "linux")]
fn impl_a() { ... }

#[cfg(not(target_os = "linux"))]
fn impl_a() { ... }            // OK - 두 cfg 가 상호 배타

6.3.9. cfg 와 모듈/파일

모듈 전체에도 cfg 를 부착할 수 있습니다.

#[cfg(target_os = "linux")]
mod linux_only {
    // 이 모듈 전체가 Linux 빌드에서만 존재
}

이때 그 모듈의 파일과 디렉토리는 디스크에 그대로 존재하지만, cfg 가 거짓이면 읽지 않거나, 읽더라도 즉시 폐기합니다.

6.3.10. cfg 안에 매크로 (없음)

cfg predicate 안에 임의의 식이나 매크로 호출을 도입할 수는 없습니다. 정해진 키-값 형태와 결합자만 허용합니다.

#[cfg(custom_macro!())]                // 채택 안 함

6.3.11. 디버깅 도구

rokc --print cfg 같은 명령으로 현재 빌드에서 활성화된 cfg 값들을 출력할 수 있습니다 (정확한 명령은 컴파일러 사양에 따릅니다).

GitHub에서 이 페이지 고치기