콘텐츠로 이동

Rust Cargo 피처와 조건부 컴파일(#[cfg])

피자를 주문하는 장면을 떠올려 보세요. 도우와 토마토소스는 누구나 똑같이 받아요. 그다음에 토핑을 골라요. 버섯, 치즈 추가, 올리브. 가게는 주문한 것만 만들어 주니까, 원하지 않는 토핑 때문에 돈을 더 내거나 더 기다릴 일이 없어요.

Rust 코드도 똑같아요. 조건부 컴파일(conditional compilation, #[cfg])을 쓰면 컴파일러에게 “이 부분은 Windows에서만 빌드해 줘”, “테스트할 때만 넣어 줘”라고 말할 수 있어요. Cargo 피처(features)를 쓰면 여러분의 크레이트를 쓰는 사람이 JSON 지원 같은 추가 기능을 필요한 만큼만 고를 수 있고요. 고르지 않은 코드는 아예 컴파일되지 않아요. 빌드를 느리게 만들지도 않고, 완성된 프로그램에 들어가지도 않아요.

이 페이지는 Cargo와 크레이트(Cargo.toml, cargo add)와 Rust 도구와 개발 환경에 이어지는 내용이에요. 이 단어들이 낯설다면 먼저 가볍게 훑어보고 오세요.

#[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!() 매크로는 같은 조건을 받지만, 결과로 평범한 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(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 시스템이에요");
}
}

실행 결과:

기본 셸: sh
64비트 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 라이브러리는 시스템마다 다른 경우가 많거든요.

사실 여러분은 이미 조건 하나를 별생각 없이 써 왔어요. 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 = "이름"으로 피처를 확인해요.

src/main.rs
#[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…” 줄을 숨겨 줄 뿐이에요.)

Terminal window
cargo run -q # 기본 피처만
cargo run -q --features json # 기본 피처 + json
cargo 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에게 물어보면 돼요.

Terminal window
cargo tree -e features --features json --depth 1

pizza_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에 다음 명령을 추가하세요.

Terminal window
cargo check --all-features
cargo check --no-default-features
cargo 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에 넣어야 해요.

메모 앱이 설정을 저장하는 위치를 돌려주는 함수 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 같은 크레이트로 이 폴더들을 제대로 찾을 거예요.

점수를 출력하는 작은 프로그램 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"]
src/main.rs
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 -q
Ana: 120점
Bo: 95점
$ cargo run -q --features export
Ana: 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