Rust Cargo 피처와 조건부 컴파일(#[cfg])
피자를 주문하는 장면을 떠올려 보세요. 도우와 토마토소스는 누구나 똑같이 받아요. 그다음에 토핑을 골라요. 버섯, 치즈 추가, 올리브. 가게는 주문한 것만 만들어 주니까, 원하지 않는 토핑 때문에 돈을 더 내거나 더 기다릴 일이 없어요.
Rust 코드도 똑같아요. 조건부 컴파일(conditional compilation, #[cfg])을 쓰면 컴파일러에게 “이 부분은 Windows에서만 빌드해 줘”, “테스트할 때만 넣어 줘”라고 말할 수 있어요. Cargo 피처(features)를 쓰면 여러분의 크레이트를 쓰는 사람이 JSON 지원 같은 추가 기능을 필요한 만큼만 고를 수 있고요. 고르지 않은 코드는 아예 컴파일되지 않아요. 빌드를 느리게 만들지도 않고, 완성된 프로그램에 들어가지도 않아요.
이 페이지는 Cargo와 크레이트(Cargo.toml, cargo add)와 Rust 도구와 개발 환경에 이어지는 내용이에요. 이 단어들이 낯설다면 먼저 가볍게 훑어보고 오세요.
#[cfg]: 코드를 남길까, 지울까
섹션 제목: “#[cfg]: 코드를 남길까, 지울까”#[cfg(...)]는 함수, 구조체, use 줄, 모듈, 심지어 문장 하나에도 붙일 수 있는 속성(attribute)이에요. 조건이 참이면 코드가 그대로 남아요. 거짓이면 컴파일러가 그 코드를 완전히 지워 버려요. 처음부터 아예 쓰지 않았던 것처럼요.
#[cfg(target_os = "linux")]fn os_name() -> &'static str { "Linux"}
#[cfg(target_os = "windows")]fn os_name() -> &'static str { "Windows"}
#[cfg(target_os = "macos")]fn os_name() -> &'static str { "macOS"}
fn main() { println!("{}에서 안녕하세요!", os_name());}실행 결과:
Linux에서 안녕하세요!Rust Playground는 Linux에서 돌아가기 때문에 Linux에서 안녕하세요!가 출력돼요. 여러분의 Windows 노트북에서 같은 코드를 실행하면 Windows에서 안녕하세요!가 나오고요. 이름이 같은 함수가 세 개나 있지만 괜찮아요. 어느 컴퓨터에서든 살아남는 건 그중 하나뿐이니까요.
그런데 여기에는 작은 함정이 있어요. FreeBSD에서는 세 조건 중 어느 것도 참이 아니라서 os_name이 아예 없어지고, 빌드가 E0425로 멈춰요. “cannot find function os_name in this scope”(이 스코프에서 os_name 함수를 찾을 수 없다)라는 에러예요. 대신 쓸 함수(fallback)를 준비하는 방법은 뒤에서 알아볼게요.
cfg!(): 간단한 참 또는 거짓
섹션 제목: “cfg!(): 간단한 참 또는 거짓”코드를 지우고 싶은 게 아니라 그냥 질문만 하고 싶을 때도 있어요. cfg!() 매크로는 같은 조건을 받지만, 결과로 평범한 bool을 돌려줘요.
fn main() { let bits = if cfg!(target_pointer_width = "64") { 64 } else { 32 }; println!("{bits}비트 프로그램이에요"); println!("가장 큰 usize: {}", usize::MAX);
if cfg!(target_os = "windows") { println!("경로는 C:\\Users\\ana처럼 생겼어요"); } else { println!("경로는 /home/ana처럼 생겼어요"); }}실행 결과:
64비트 프로그램이에요가장 큰 usize: 18446744073709551615경로는 /home/ana처럼 생겼어요target_pointer_width는 메모리 주소의 크기예요. 요즘 컴퓨터는 거의 다 "64"이고, 일부 작은 기기는 "32"예요. 이 값은 usize의 크기이기도 해요. 그래서 여기서 usize::MAX가 이렇게 큰 거예요.
그럼 언제 어떤 걸 써야 할까요?
#[cfg(...)] |
cfg!(...) |
|
|---|---|---|
| 하는 일 | 거짓이면 코드를 지워요 | true 또는 false가 돼요 |
| “거짓” 쪽 코드도 컴파일되나요? | 아니요, 사라져요 | 네, 두 분기 모두 어디서나 컴파일돼야 해요 |
| 이럴 때 써요 | 다른 곳에서는 컴파일할 수 없는 코드(특정 OS 전용 API, 선택적 크레이트) | 동작이 조금 다를 때 |
가운데 줄이 중요해요. std::os::unix::fs::PermissionsExt 같은 Unix 전용 기능은 Windows에 없어요. 그래서 이걸 쓰는 코드는 반드시 #[cfg(unix)] 뒤에 숨겨야 해요. if cfg!(unix)만으로는 부족해요. Windows의 컴파일러가 그 분기도 컴파일하려고 하거든요.
조건 조합하기: not, any, all
섹션 제목: “조건 조합하기: not, any, all”작은 조건을 모아서 더 큰 조건을 만들 수 있어요.
not(a):a가 거짓일 때 참any(a, b, ...): 하나라도 참이면 참(“또는”과 같아요)all(a, b, ...): 모두 참이어야 참(“그리고”와 같아요)
아까 본 OS 예제를 다시 볼게요. 이번에는 어디서나 컴파일되도록 대신 쓸 함수를 넣었어요.
#[cfg(any(target_os = "linux", target_os = "macos"))]fn shell() -> &'static str { "sh"}
#[cfg(target_os = "windows")]fn shell() -> &'static str { "cmd"}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]fn shell() -> &'static str { "unknown"}
fn main() { println!("기본 셸: {}", shell());
if cfg!(all(unix, target_pointer_width = "64")) { println!("64비트 Unix 시스템이에요"); }}실행 결과:
기본 셸: sh64비트 Unix 시스템이에요unix와 windows는 편리한 짧은 이름이에요. unix는 Linux, macOS, BSD 계열 같은 시스템에서 참이에요. 가장 자주 만나는 조건들을 정리해 볼게요.
| 조건 | 참이 되는 경우 |
|---|---|
target_os = "linux" / "windows" / "macos" |
그 운영체제용으로 빌드할 때 |
unix / windows |
Unix 계열 시스템용 / Windows용으로 빌드할 때 |
target_arch = "x86_64" / "aarch64" / "wasm32" |
그 종류의 프로세서용으로 빌드할 때(WebAssembly는 wasm32) |
target_pointer_width = "64" |
주소(와 usize)가 64비트일 때 |
debug_assertions |
--release가 아닌 디버그 빌드(cargo build)일 때 |
test |
cargo test를 실행할 때 |
feature = "json" |
Cargo 피처 json이 켜져 있을 때(아래에서 자세히 설명해요) |
내 컴퓨터의 전체 목록이 궁금하다면 rustc --print cfg를 실행해 보세요. 64비트 Linux PC에서는 target_os="linux", target_family="unix", debug_assertions 같은 줄이 출력돼요. FFI 코드에서는 #[cfg(windows)]와 #[cfg(unix)]를 정말 자주 보게 될 거예요. C 라이브러리는 시스템마다 다른 경우가 많거든요.
#[cfg(test)]와 #[cfg_attr]
섹션 제목: “#[cfg(test)]와 #[cfg_attr]”사실 여러분은 이미 조건 하나를 별생각 없이 써 왔어요. Rust 테스트 페이지의 테스트 모듈은 모두 #[cfg(test)]로 시작하죠.
pub fn add_points(total: u32, points: u32) -> u32 { total + points}
#[cfg(test)]mod tests { use super::*;
#[test] fn adds_points() { assert_eq!(add_points(10, 5), 15); }}cargo test는 test를 켜기 때문에 이 모듈이 빌드되고 테스트가 실행돼요. 평범한 cargo build는 모듈 전체를 빼 버려요. 그래서 실제 프로그램에는 테스트 코드가 하나도 들어가지 않아요. #[cfg]는 이렇게 어떤 모듈에든 붙일 수 있어요(Rust 모듈 참고). 예를 들면 #[cfg(windows)] mod windows_paths;처럼요.
#[cfg_attr(조건, 속성)]은 “조건이 참이면 이 속성을 붙여 줘”라는 뜻이에요. 가끔만 필요한 derive에 자주 써요.
#[cfg_attr(debug_assertions, derive(Debug))]struct Player { name: String, score: u32,}
fn main() { let player = Player { name: "Ana".to_string(), score: 120 };
// 디버그 빌드에서만 실행돼요. stdout이 아니라 stderr로 출력해요 #[cfg(debug_assertions)] eprintln!("debug: {:?}", player);
println!("{}의 점수: {}점", player.name, player.score);}실행 결과:
Ana의 점수: 120점디버그 빌드(Playground의 기본값)에서는 출력의 “Standard Error” 부분에 debug: Player { name: "Ana", score: 120 }도 보여요. Playground를 “Release”로 바꾸면 그 줄이 사라지고, Player의 Debug 코드도 함께 사라져요. eprintln!에 따로 #[cfg(debug_assertions)]를 붙인 것도 눈여겨보세요. 이게 없으면 릴리스 빌드가 실패해요. 릴리스 빌드에서는 Player가 Debug를 구현하지 않으니까요. 디버그 빌드와 릴리스 빌드는 Cargo와 크레이트에서 더 자세히 다뤄요.
내 크레이트에서 Cargo 피처 쓰기
섹션 제목: “내 크레이트에서 Cargo 피처 쓰기”드디어 피자 메뉴 차례예요. 피처(feature)는 Cargo.toml의 [features] 테이블에 정의하는, 이름이 붙은 스위치예요. 피처가 두 개 있는 작은 프로그램 pizza_order를 만들어 볼게요.
extra_cheese: 무료 토핑이에요. 기본으로 켜져 있어요.json: 주문을 JSON으로 출력해요.serde와serde_json크레이트가 필요하지만, 누군가json을 요청할 때만 내려받고 컴파일해요.
[package]name = "pizza_order"version = "0.1.0"edition = "2024"
[dependencies]serde = { version = "1", features = ["derive"], optional = true }serde_json = { version = "1", optional = true }
[features]default = ["extra_cheese"]extra_cheese = []json = ["dep:serde", "dep:serde_json"]한 줄씩 살펴볼게요.
optional = true: “피처가 요청하지 않으면 이 크레이트는 쓰지 않는다”는 뜻이에요.extra_cheese = []: 다른 건 아무것도 켜지 않는 피처를 만들어요. 코드에서 확인할 수 있는 스위치일 뿐이에요.json = ["dep:serde", "dep:serde_json"]: “json이 켜지면 이 두 선택적 의존성도 켜라”는 뜻이에요.dep:는 “이건 피처 이름이 아니라 의존성이에요”라는 표시예요.default = [...]: 아무도 따로 말하지 않았을 때 켜지는 피처 목록이에요.
코드에서는 feature = "이름"으로 피처를 확인해요.
#[cfg_attr(feature = "json", derive(serde::Serialize))]struct Order { size: String, toppings: Vec<String>,}
fn main() { let mut toppings = vec!["토마토".to_string(), "바질".to_string()];
if cfg!(feature = "extra_cheese") { toppings.push("치즈 추가".to_string()); }
let order = Order { size: "라지".to_string(), toppings }; println!("주문: {} 피자 ({})", order.size, order.toppings.join(", "));
#[cfg(feature = "json")] println!("JSON 형식: {}", serde_json::to_string(&order).unwrap());}JSON 줄에는 cfg!가 아니라 #[cfg]를 썼어요. json 피처가 없으면 serde_json은 아예 의존성이 아니니까, 그 줄은 완전히 사라져야 하거든요. 치즈 확인은 어느 쪽이든 문제없이 컴파일되니까 cfg!로 충분해요.
이제 명령줄에서 피자를 주문해 볼게요. (-q는 Cargo의 “Compiling…” 줄을 숨겨 줄 뿐이에요.)
cargo run -q # 기본 피처만cargo run -q --features json # 기본 피처 + jsoncargo run -q --no-default-features # 추가 없음, 기본 피처도 없음cargo run -q --all-features # 모든 피처실제 출력은 이래요.
$ cargo run -q주문: 라지 피자 (토마토, 바질, 치즈 추가)
$ cargo run -q --features json주문: 라지 피자 (토마토, 바질, 치즈 추가)JSON 형식: {"size":"라지","toppings":["토마토","바질","치즈 추가"]}
$ cargo run -q --no-default-features주문: 라지 피자 (토마토, 바질)
$ cargo run -q --all-features주문: 라지 피자 (토마토, 바질, 치즈 추가)JSON 형식: {"size":"라지","toppings":["토마토","바질","치즈 추가"]}이 옵션들은 함께 쓸 수 있어요. --no-default-features --features json이면 치즈 없이 JSON이 나와요. 피처를 여러 개 지정할 때는 --features json,extra_cheese처럼 쉼표로 구분해요. 같은 옵션을 cargo build, cargo check, cargo test에서도 쓸 수 있어요.
의존성의 피처 켜기
섹션 제목: “의존성의 피처 켜기”대부분은 카운터 반대편에 서 있을 거예요. 피처가 있는 크레이트를 쓰는 쪽 말이에요. Cargo와 크레이트에서도 이런 줄을 이미 봤어요.
[dependencies]serde = { version = "1", features = ["derive"] }tokio = { version = "1", features = ["full"] }serde_json = { version = "1", features = ["preserve_order"] }serde의derive피처를 켜면#[derive(Serialize, Deserialize)]를 쓸 수 있어요.tokio에는 기본 피처가 없어요. 그것만으로는 거의 아무것도 할 수 없어요.full은 런타임,#[tokio::main], 타이머, 네트워크, 파일 등 모든 걸 켜요. 공부할 때는 이게 편해요. 실제 프로젝트에서는features = ["rt-multi-thread", "macros"]처럼 필요한 것만 요청할 수도 있어요.serde_json의preserve_order는 객체의 키를 넣은 순서 그대로 유지해요.
이런 줄은 cargo add가 대신 써 줘요. 예를 들면 cargo add tokio --features full처럼요.
크레이트는 왜 이렇게 기능을 나눠 놓을까요? 쓰지 않는 부분은 내려받을 필요도, 컴파일할 필요도, 배포할 필요도 없기 때문이에요. tokio 같은 큰 크레이트가 매번 모든 걸 컴파일한다면 작은 프로젝트도 빌드가 느려질 거예요. 피처 덕분에 컴파일 시간과 바이너리 크기를 작게 유지할 수 있어요.
default-features = false를 쓰면 크레이트의 기본 피처를 끄고 필요한 것만 고를 수도 있어요.
[dependencies]serde_json = { version = "1", default-features = false, features = ["alloc"] }프로젝트에서 어떤 피처가 켜져 있는지는 Cargo에게 물어보면 돼요.
cargo tree -e features --features json --depth 1pizza_order에서는 이렇게 출력돼요.
pizza_order v0.1.0 (/home/ana/pizza_order)├── serde feature "default"├── serde feature "derive"└── serde_json feature "default"--depth 1을 빼면 의존성이 자기 의존성에서 켠 피처까지 포함해서 트리 전체가 보여요. 길긴 하지만 “이 크레이트는 대체 왜 컴파일되는 거지?” 싶을 때 가장 믿음직한 도구예요.
기억해 둘 원칙
섹션 제목: “기억해 둘 원칙”피처는 기능을 더하기만 해야 해요. 피처를 켰다고 함수가 사라지거나 기존 함수의 동작이 바뀌면 안 돼요. 이유는 이래요. 프로젝트 안의 두 크레이트가 모두 serde_json을 쓰는데, 한쪽은 preserve_order를 요청하고 다른 쪽은 요청하지 않았다고 해 볼게요. 이때 Cargo는 두 쪽의 피처를 모두 켠 serde_json을 딱 한 번만 컴파일해요. 그러니 어떤 크레이트든 다른 누군가가 추가 피처를 켜더라도 계속 잘 동작해야 해요.
동시에 켤 수 없는 피처는 피하세요. “postgres 피처 아니면 sqlite 피처, 둘 다는 안 됨” 같은 설계는 두 크레이트가 서로 다른 쪽을 고르는 순간 깨지고, --all-features도 쓸 수 없게 돼요. 한꺼번에 모두 켤 수 있는 피처로 만드세요.
피처를 문서로 남기세요. README나 크레이트 문서에 피처 이름, 무엇이 추가되는지, 기본으로 켜져 있는지를 짧게 적어 두세요. docs.rs(영어)에서는 모든 크레이트에 “Feature flags” 페이지가 있고(예: tokio의 페이지(영어)), 많은 크레이트가 항목에 “Available on crate feature … only”(크레이트 피처 …가 켜져 있을 때만 쓸 수 있음) 표시를 달아 둬요.
CI에서 모든 조합을 확인하세요. 피처 안쪽의 코드는 그 피처가 켜져 있을 때만 컴파일돼요. 거기에 문제가 숨어 있지 않도록 CI에 다음 명령을 추가하세요.
cargo check --all-featurescargo check --no-default-featurescargo test --all-features큰 크레이트라면 cargo-hack 도구로 피처를 하나씩 따로 확인할 수 있어요(cargo hack check --each-feature).
자주 하는 실수
섹션 제목: “자주 하는 실수”피처 이름 오타. #[cfg(feature = "jsno")]라고 써도 코드는 컴파일돼요. --features json을 붙여도 JSON 줄이 그냥 출력되지 않을 뿐이에요. 다행히 요즘 Rust는 경고를 보여 줘요.
warning: unexpected `cfg` condition value: `jsno` --> src/main.rs:17:11 |17 | #[cfg(feature = "jsno")] | ^^^^^^^^^^------ | | | help: there is a expected value with a similar name: `"json"` | = note: expected values for `feature` are: `default`, `extra_cheese`, and `json` = help: consider adding `jsno` as a feature in `Cargo.toml` = note: `#[warn(unexpected_cfgs)]` on by default이 경고는 “jsno는 예상하지 못한 값이고, 비슷한 이름의 "json"이 있어요”라고 알려 주고, 있는 피처 목록도 보여 줘요. 이 경고를 무시하지 마세요. 명령줄에서는 Cargo가 더 엄격해요. cargo run --features jsno는 error: the package 'pizza_order' does not contain this feature: jsno(패키지에 이 피처가 없다)라는 에러로 멈추고 json을 제안해 줘요.
피처 이름 대신 의존성 이름을 확인하기. dep:serde라고 썼기 때문에 pizza_order에는 serde라는 피처가 없어요. #[cfg_attr(feature = "serde", ...)]라고 쓰면 똑같은 “unexpected cfg condition value” 경고가 이번에는 serde에 대해 나와요. 게다가 cargo run --features json은 E0277로 실패해요. “the trait bound Order: serde::Serialize is not satisfied”(Order가 serde::Serialize를 구현하지 않았다)라는 에러예요. derive가 한 번도 붙지 않았기 때문이에요. 확인할 이름은 [features]에 있는 이름, 즉 feature = "json"이에요.
dep: 빼먹기. 어느 피처에서든 dep:serde라고 썼다면 serde에는 모든 곳에서 dep:를 붙여야 해요. 새 피처 yaml = ["serde"]를 추가하면 Cargo가 파일 읽기를 거부해요.
error: failed to parse manifest at `/home/ana/pizza_order/Cargo.toml`
Caused by: feature `yaml` includes `serde`, but `serde` is an optional dependency without an implicit feature Use `dep:serde` to enable the dependency.Cargo는 “serde는 암묵적인 피처가 없는 선택적 의존성이니까 dep:serde를 쓰세요”라고 말하고 있어요. 고치는 방법은 yaml = ["dep:serde"]예요. 참고로 어디에도 dep:를 쓰지 않으면 Cargo는 선택적 의존성마다 공개 피처를 조용히 만들어 줘요(이 예제라면 --features serde가 동작해요). 이건 예전 방식이에요. dep:를 쓰면 피처 목록이 깔끔하고, 의도한 것만 남아요.
한 번도 컴파일되지 않는 코드. #[cfg(feature = "json")] 줄에서 실수로 serde_json::to_strng이라고 썼다고 해 볼게요. 그 줄은 기본으로는 컴파일되지 않기 때문에 cargo check는 아무 문제 없다고 해요. cargo check --all-features만 이걸 찾아내요.
error[E0425]: cannot find function `to_strng` in crate `serde_json` --> src/main.rs:18:41 |18 | println!("JSON 형식: {}", serde_json::to_strng(&order).unwrap()); | ^^^^^^^^ |help: a function with a similar name exists |18 | println!("JSON 형식: {}", serde_json::to_string(&order).unwrap()); | +컴파일러는 “serde_json에서 to_strng 함수를 찾을 수 없다”고 하면서 비슷한 이름의 to_string을 제안해요. 바로 이런 이유로 --all-features를 CI에 넣어야 해요.
연습 문제
섹션 제목: “연습 문제”1. OS마다 다른 설정 폴더
섹션 제목: “1. OS마다 다른 설정 폴더”메모 앱이 설정을 저장하는 위치를 돌려주는 함수 config_dir()를 작성해 보세요. Linux에서는 "~/.config/notes", macOS에서는 "~/Library/Application Support/notes", Windows에서는 r"%APPDATA%\notes", 그 밖의 모든 시스템에서는 "./notes"를 돌려줘요. 결과는 main에서 출력해요. 힌트: 경우마다 #[cfg(target_os = "...")]를 붙인 함수를 하나씩 만들고, 대신 쓸 함수에는 not(any(...))를 쓰세요.
정답 보기
#[cfg(target_os = "linux")]fn config_dir() -> &'static str { "~/.config/notes"}
#[cfg(target_os = "macos")]fn config_dir() -> &'static str { "~/Library/Application Support/notes"}
#[cfg(target_os = "windows")]fn config_dir() -> &'static str { r"%APPDATA%\notes"}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]fn config_dir() -> &'static str { "./notes"}
fn main() { println!("설정 저장 위치: {}", config_dir());}실행 결과:
설정 저장 위치: ~/.config/notes어느 컴퓨터에서든 네 함수 중 정확히 하나만 살아남으니까, main은 언제나 config_dir를 찾을 수 있어요. Playground는 Linux에서 돌아가서 Linux 경로가 보여요. 실제 앱이라면 dirs 같은 크레이트로 이 폴더들을 제대로 찾을 거예요.
2. 선택적인 JSON 내보내기
섹션 제목: “2. 선택적인 JSON 내보내기”점수를 출력하는 작은 프로그램 leaderboard가 있어요. serde_json을 가져오고 export_json 함수를 추가하는 피처 export(기본으로는 꺼짐)를 만들어 보세요. --features export를 붙이면 점수를 JSON으로도 출력해야 해요. 힌트: serde_json을 선택적 의존성으로 만들고, 피처에는 dep:serde_json을 쓰고, 함수와 그 함수를 부르는 줄 모두에 #[cfg(feature = "export")]를 붙이세요. Playground에서는 Cargo 피처를 지정할 수 없으니, 이 문제는 내 컴퓨터에서 cargo new leaderboard로 시작해 보세요.
정답 보기
[package]name = "leaderboard"version = "0.1.0"edition = "2024"
[dependencies]serde_json = { version = "1", optional = true }
[features]export = ["dep:serde_json"]struct Score { name: &'static str, points: u32,}
#[cfg(feature = "export")]fn export_json(scores: &[Score]) -> String { let list: Vec<serde_json::Value> = scores .iter() .map(|s| serde_json::json!({ "name": s.name, "points": s.points })) .collect(); serde_json::Value::Array(list).to_string()}
fn main() { let scores = [ Score { name: "Ana", points: 120 }, Score { name: "Bo", points: 95 }, ];
for s in &scores { println!("{}: {}점", s.name, s.points); }
#[cfg(feature = "export")] println!("내보내기: {}", export_json(&scores));}$ cargo run -qAna: 120점Bo: 95점
$ cargo run -q --features exportAna: 120점Bo: 95점내보내기: [{"name":"Ana","points":120},{"name":"Bo","points":95}]default = [...] 줄이 없으니까 export는 요청하지 않으면 꺼져 있어요. 꺼져 있을 때는 serde_json을 내려받지도 않아요. 함수와 호출하는 줄 모두에 #[cfg]가 필요한 건, 둘 다 serde_json을 쓰기 때문이에요.
#[cfg(조건)]은 조건이 참일 때만 코드를 남겨요. 거짓이면 컴파일하기 전에 지워져요.cfg!(조건)은 그냥bool이에요. 두 분기 모두 모든 플랫폼에서 컴파일될 수 있어야 해요.- 조건은
not(..),any(..),all(..)로 조합해요. 자주 쓰는 조건은target_os,unix/windows,debug_assertions,test예요. #[cfg_attr(조건, 속성)]은 조건이 참일 때만 속성을 붙여요.- Cargo 피처는
[features]에 적어요. 선택적 크레이트에는optional = true와dep:이름을, 기본으로 켤 것에는default = [...]를 써요. - 피처는 기능을 더하기만 해야 해요. CI에서
cargo check --all-features를 실행하고,unexpected_cfgs경고를 놓치지 마세요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. #[cfg(target_os = "windows")]가 붙은 함수는 Linux에서 빌드하면 어떻게 될까요?
2. Unix 전용 API를 쓰는 코드에 if cfg!(unix) { ... }만으로는 부족한 이유는 뭘까요?
3. Cargo.toml에서 json = ["dep:serde_json"]은 무슨 일을 할까요?
4. 실수로 #[cfg(feature = "jsno")]라고 썼어요. 어떻게 될까요?
점수: 0 / 4
더 알아보기
섹션 제목: “더 알아보기”- Features — The Cargo Book(영어):
[features], 선택적 의존성,dep:, 그리고 피처가 어떻게 합쳐지는지까지 다루는 완전한 가이드예요. - Conditional compilation — The Rust Reference(영어): 모든
cfg옵션과#[cfg],#[cfg_attr],cfg!의 정확한 규칙이 나와 있어요. cfg!매크로 — Rust 표준 라이브러리(영어):cfg!가 무엇을 돌려주는지,#[cfg]속성과 어떻게 다른지 설명해요.- Specifying Dependencies — The Cargo Book(영어): 의존성마다
features,default-features = false,optional을 쓰는 방법이에요. - cfg — Rust by Example(영어):
#[cfg]와cfg!를 나란히 보여 주는 짧은 예제가 있어요.