cargo clippy로 Rust 코드 품질 높이기
Rust 컴파일러는 워낙 꼼꼼해서 컴파일만 통과하면 꽤 안심이 되죠. 하지만 “컴파일은 되는데 이게 정말 좋은 코드인가?”라는 질문에는 컴파일러가 답해주지 않습니다. 불필요한 clone() 호출, 더 간결하게 쓸 수 있는 패턴, 성능을 깎아먹는 습관적인 코드… 이런 건 컴파일러의 관심사가 아니거든요.
Clippy는 바로 이 영역을 담당하는 Rust의 공식 린트 도구입니다. 700개가 넘는 린트 규칙으로 코드를 분석해서 잠재적인 버그부터 스타일 개선 사항까지 짚어줍니다. 이번 글에서는 Clippy의 설치와 기본 사용법, 주요 린트 카테고리, 프로젝트에 맞게 린트를 조정하는 방법, 그리고 CI에서 활용하는 방법까지 차근차근 살펴보겠습니다.
Clippy가 뭔가요?
Clippy는 Rust 코드에 대한 정적 분석 도구입니다. 이름은 마이크로소프트 오피스의 그 클립 모양 도우미에서 따온 건데, “이걸 이렇게 바꾸면 어떨까요?” 하고 제안해주는 역할이 딱 비슷해요 😅
컴파일러가 “이 코드가 문법적으로 맞는가, 타입이 올바른가”를 검사한다면, Clippy는 한 단계 더 나아가서 “이 코드가 관용적인가, 더 나은 방법이 있지 않은가”를 검사합니다. 예를 들어 이런 코드를 생각해볼게요.
let result = if condition { true } else { false };
이 코드는 완벽하게 유효한 Rust 코드지만, 사실 아래처럼 쓰면 되거든요.
let result = condition;
컴파일러는 아무 말 없이 통과시키지만 Clippy는 “이거 그냥 condition을 직접 쓰면 되지 않나요?”라고 알려줍니다. 이런 식으로 코드 리뷰에서 사람이 지적할 법한 것들을 자동으로 잡아주는 거예요.
설치하기
Clippy는 Rust 도구 체인의 공식 컴포넌트라서 rustup으로 간단하게 설치할 수 있습니다.
rustup component add clippy
최근 버전의 rustup으로 Rust를 설치했다면 Clippy가 이미 포함되어 있을 가능성이 높아요. 설치 여부를 확인하려면 다음 명령어를 실행해보세요.
cargo clippy --version
버전 정보가 출력되면 이미 설치된 겁니다. nightly 도구 체인을 사용하고 있다면 nightly 전용 Clippy를 따로 추가해야 할 수도 있어요.
rustup component add clippy --toolchain nightly
기본 사용법
사용법은 정말 간단합니다. 프로젝트 디렉토리에서 이 명령어 하나면 돼요.
cargo clippy
이 명령어는 내부적으로 cargo check와 비슷하게 동작하면서 Clippy의 린트 규칙을 추가로 적용합니다. 문제가 없으면 깔끔하게 끝나고, 개선할 부분이 있으면 구체적인 위치와 제안을 보여줘요.
예를 들어 이런 코드가 있다고 해볼게요.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
if doubled.len() == 0 {
println!("비어 있습니다");
}
for i in 0..doubled.len() {
println!("{}", doubled[i]);
}
}
cargo clippy를 실행하면 이런 경고가 나옵니다.
warning: length comparison to zero
--> src/main.rs:5:8
|
5 | if doubled.len() == 0 {
| ^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `doubled.is_empty()`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
= note: `#[warn(clippy::len_zero)]` on by default
warning: the loop variable `i` is only used to index `doubled`
--> src/main.rs:9:14
|
9 | for i in 0..doubled.len() {
| ^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
= note: `#[warn(clippy::needless_range_loop)]` on by default
help: consider using an iterator
|
9 | for item in &doubled {
| ~~~~ ~~~~~~~~
각 경고에는 어떤 린트가 적용되었는지(clippy::len_zero, clippy::needless_range_loop), 어떻게 고치면 좋은지, 그리고 린트에 대한 상세 문서 링크까지 포함되어 있어서 바로 이해하고 수정할 수 있습니다.
자동 수정 적용하기
Clippy가 제안하는 수정 사항 중 상당수는 자동으로 적용할 수 있습니다. --fix 플래그를 사용하면 돼요.
cargo clippy --fix
이 명령어는 기계적으로 치환할 수 있는 수정 사항을 코드에 직접 반영합니다. len() == 0을 is_empty()로 바꾸는 것처럼 명확한 변환은 자동으로 처리되지만, 의미가 달라질 수 있는 변환은 건너뛰어요.
만약 이미 커밋하지 않은 변경 사항이 있는 상태에서 자동 수정을 적용하고 싶다면 --allow-dirty 옵션을 추가합니다.
cargo clippy --fix --allow-dirty
자동 수정을 적용한 뒤에는 반드시 코드를 확인하고 테스트를 돌려보세요. 대부분 안전하지만 의도와 다른 변환이 있을 수 있으니까요.
주요 린트 카테고리
Clippy의 700개가 넘는 린트는 성격에 따라 여러 카테고리로 분류되어 있습니다. 각 카테고리마다 기본 활성화 수준이 다르고, 프로젝트 성격에 따라 조정할 수 있어요.
clippy::correctness— 거의 확실하게 버그인 코드를 잡아냅니다. 무한 루프, 잘못된 비교, 사용되지 않는unsafe등이 해당됩니다. 기본적으로 deny 수준이라 경고가 아니라 에러로 처리돼요.clippy::suspicious— 버그는 아닐 수 있지만 의심스러운 패턴입니다. 의도하지 않은 가변 참조나 예상과 다르게 동작할 수 있는 연산자 우선순위 같은 것들이요.clippy::style— Rust 관용적 스타일에서 벗어난 코드입니다.len() == 0대신is_empty(),if let Some(x) = ...대신 패턴 매칭 같은 스타일 제안이 포함됩니다.clippy::complexity— 불필요하게 복잡한 코드를 단순화하라는 제안입니다. 중복된 클로저, 필요 없는 형변환, 과도한 참조 해제 등을 지적해요.clippy::perf— 성능에 영향을 줄 수 있는 패턴입니다. 불필요한clone(),collect()후 바로 소비하는 이터레이터, 비효율적인 문자열 연산 같은 것들을 잡아냅니다.clippy::pedantic— 더 엄격한 린트 모음입니다. 기본적으로 꺼져 있고, 켜면 아주 세밀한 부분까지 지적합니다. 함수 시그니처의 일관성, 문서화 누락, 와일드카드 임포트 등이 포함돼요.clippy::nursery— 아직 실험 단계인 린트들입니다. 유용하지만 오탐(false positive)이 있을 수 있어서 기본적으로 꺼져 있어요.clippy::cargo—Cargo.toml관련 린트입니다. 누락된 메타데이터, 피처 플래그 문제, 와일드카드 의존성 같은 패키지 설정 문제를 검사합니다.
이 중에서 correctness, suspicious, style, complexity, perf는 기본적으로 활성화되어 있고, pedantic, nursery, cargo는 명시적으로 켜야 합니다.
린트 그룹 활성화하기
기본 린트만으로도 꽤 많은 문제를 잡아주지만, 좀 더 엄격하게 검사하고 싶다면 추가 린트 그룹을 켤 수 있습니다. 명령줄에서 직접 지정하는 방법이 가장 간단해요.
# pedantic 린트까지 포함
cargo clippy -- -W clippy::pedantic
# cargo 관련 린트도 함께
cargo clippy -- -W clippy::pedantic -W clippy::cargo
-- 뒤에 오는 부분이 Clippy에 직접 전달되는 플래그입니다. -W는 warn(경고) 수준으로 활성화한다는 뜻이에요.
소스 코드에서 크레이트 전체에 적용하려면 main.rs나 lib.rs 맨 위에 어트리뷰트를 추가합니다.
#![warn(clippy::pedantic)]
#![warn(clippy::cargo)]
pedantic 린트를 켜면 경고가 꽤 많이 뜰 수 있는데, 그중 프로젝트에 맞지 않는 것만 개별적으로 끄면 됩니다.
특정 린트 허용하거나 거부하기
프로젝트를 진행하다 보면 특정 린트가 맞지 않는 경우가 있어요. Clippy는 여러 수준에서 린트를 제어할 수 있습니다.
코드의 특정 부분에서만 린트를 끄고 싶다면 #[allow] 어트리뷰트를 사용합니다.
// 이 함수에서만 too_many_arguments 경고 무시
#[allow(clippy::too_many_arguments)]
fn create_user(
name: &str,
email: &str,
age: u32,
city: &str,
country: &str,
phone: &str,
role: &str,
active: bool,
) {
// ...
}
반대로 특정 린트를 에러로 처리하고 싶다면 #[deny]를 씁니다.
// 이 모듈에서 unwrap 사용하면 컴파일 에러
#[deny(clippy::unwrap_used)]
mod api {
// unwrap() 쓰면 컴파일이 안 됨
}
크레이트 전체에 적용하려면 #![warn(...)]이나 #![deny(...)]를 루트 파일에 넣으면 돼요.
// 크레이트 전체에서 unwrap, expect 사용 경고
#![warn(clippy::unwrap_used)]
#![warn(clippy::expect_used)]
// pedantic 전체 활성화하되 일부만 허용
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]
이렇게 하면 pedantic 린트를 전반적으로 활용하면서도 프로젝트에 안 맞는 규칙만 빼놓을 수 있습니다.
clippy.toml로 린트 설정하기
일부 린트는 동작 방식을 세밀하게 조정할 수 있는데, 이때 프로젝트 루트에 clippy.toml 파일을 만들어 설정합니다.
# 함수 매개변수가 이 개수를 넘으면 경고
too-many-arguments-threshold = 10
# 타입 복잡도가 이 수준을 넘으면 경고
type-complexity-threshold = 350
# 인지 복잡도 임계값
cognitive-complexity-threshold = 30
# 테스트 코드에서 dbg! 매크로 사용 허용
allow-dbg-in-tests = true
too-many-arguments-threshold가 대표적인 예인데요. 기본값은 7이지만 프로젝트 특성에 따라 조정할 수 있어요. 빌더 패턴으로 리팩터링하는 게 정석이지만, FFI 바인딩처럼 매개변수가 많을 수밖에 없는 상황도 있으니까요.
코드에서 자주 만나는 Clippy 경고
실제로 Rust 코드를 작성하면서 자주 마주치는 Clippy 경고 몇 가지를 살펴볼게요.
가장 많이 만나는 건 clippy::needless_return입니다. Rust는 마지막 표현식이 자동으로 반환값이 되는데, 불필요한 return을 쓰면 이 경고가 뜹니다.
// 경고 대상
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
// Clippy 제안
fn add(a: i32, b: i32) -> i32 {
a + b
}
clippy::single_match도 흔합니다. match 문에 패턴이 하나뿐이면 if let이 더 간결하거든요.
// 경고 대상
match result {
Ok(value) => println!("{value}"),
_ => {},
}
// Clippy 제안
if let Ok(value) = result {
println!("{value}");
}
clippy::manual_map도 자주 보이는데, match로 Option을 변환하는 코드를 map()으로 간결하게 쓸 수 있을 때 나타납니다.
// 경고 대상
let output = match input {
Some(x) => Some(x * 2),
None => None,
};
// Clippy 제안
let output = input.map(|x| x * 2);
성능 쪽에서는 clippy::unnecessary_to_owned를 눈여겨볼 만합니다. 참조만 필요한 곳에서 불필요한 소유권 변환을 지적해요.
// 경고 대상
fn greet(name: &str) {
println!("Hello, {name}!");
}
let name = String::from("Alice");
greet(&name.to_string()); // 이미 String인데 또 to_string()
// Clippy 제안
greet(&name);
이런 경고들을 하나씩 고치다 보면 자연스럽게 Rust의 관용적 코드 스타일이 몸에 배게 됩니다.
CI에서 활용하기
Clippy의 진가는 CI에 연동해서 모든 PR에 자동으로 적용할 때 나옵니다. 핵심은 -D warnings 플래그인데, 이걸 붙이면 모든 경고를 에러로 취급해서 CI를 실패시킵니다.
cargo clippy -- -D warnings
GitHub Actions 워크플로우로 설정하면 이렇게 됩니다.
name: Clippy
on:
push:
branches: [main]
pull_request:
jobs:
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy -- -D warnings
이게 전부예요. PR이 올라올 때마다 Clippy가 돌아가고, 경고가 하나라도 있으면 CI가 실패합니다.
pedantic 린트까지 CI에서 검사하고 싶다면 이렇게 확장하면 됩니다.
- run: cargo clippy -- -D warnings -W clippy::pedantic
다만 pedantic을 CI에 넣으려면 팀원 전체가 합의해야 해요. 너무 엄격하면 오히려 생산성이 떨어질 수 있으니까, 처음에는 기본 린트로 시작해서 점진적으로 늘려가는 게 좋습니다.
cargo fmt와 함께 쓰기
코드 품질을 챙기려면 Clippy와 함께 cargo fmt도 빠질 수 없습니다. 둘의 역할은 명확하게 다른데, cargo fmt는 코드의 포맷팅(들여쓰기, 줄 바꿈, 공백)을 통일하고, Clippy는 코드의 내용(패턴, 관용성, 성능)을 검사합니다.
CI에서는 보통 두 검사를 같이 돌립니다.
name: Lint
on:
push:
branches: [main]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- run: cargo fmt --check
- run: cargo clippy -- -D warnings
cargo fmt --check는 포맷팅이 맞지 않으면 0이 아닌 종료 코드를 반환해서 CI를 실패시키고, 뒤이어 Clippy가 코드 내용을 검사하는 순서입니다.
로컬에서는 커밋하기 전에 이 두 가지를 습관적으로 돌려주면 CI에서 불필요하게 실패하는 일을 줄일 수 있어요.
cargo fmt && cargo clippy -- -D warnings
실전 팁
써보면서 느낀 점 몇 가지를 공유할게요.
새로운 프로젝트를 시작할 때 lib.rs나 main.rs 맨 위에 원하는 린트 정책을 미리 정해두세요. 나중에 추가하면 기존 코드에서 경고가 쏟아져서 고치기가 번거롭거든요. 처음부터 설정해두면 코드가 쌓이면서 자연스럽게 규칙을 따르게 됩니다.
clippy::unwrap_used와 clippy::expect_used는 라이브러리 크레이트에서 특히 유용합니다. 라이브러리가 unwrap()으로 패닉을 일으키면 사용하는 쪽에서 대처할 수 없으니까, 이 린트를 warn이나 deny로 설정해두면 안전한 에러 처리를 강제할 수 있어요.
Clippy 경고가 잘 이해되지 않을 때는 경고 메시지에 포함된 링크를 클릭해보세요. 각 린트마다 왜 이 패턴이 문제인지, 어떻게 고치면 좋은지 상세한 설명과 예제가 있습니다.
#[allow]를 남용하지 마세요. 경고를 끄는 게 가장 쉬운 해결책이지만, 대부분의 경우 Clippy가 제안하는 방향이 더 나은 코드입니다. 정말 타당한 이유가 있을 때만 #[allow]를 쓰고, 가능하면 왜 끄는지 주석으로 남겨두는 게 좋습니다.
// FFI 함수라 매개변수 수를 줄일 수 없음
#[allow(clippy::too_many_arguments)]
extern "C" fn ffi_callback(...) { ... }
마지막으로, Clippy는 Rust 도구 체인과 함께 업데이트되기 때문에 새로운 린트가 꾸준히 추가됩니다. rustup update를 주기적으로 실행해서 최신 린트를 적용받으세요. 가끔 업데이트 후에 새 경고가 뜨는데, 그게 코드를 개선할 기회이기도 합니다.
마치며
Clippy는 Rust 개발자라면 꼭 써봐야 할 도구입니다. 컴파일러가 잡지 못하는 비관용적 패턴이나 성능 문제를 자동으로 찾아주니까 코드 리뷰 부담도 줄어들고 전체적인 코드 품질도 올라가요.
CI에 -D warnings 플래그 하나만 추가하면 팀 전체의 코드 품질 기준선을 유지할 수 있고, cargo semver-checks나 release-plz 같은 도구와 함께 쓰면 린트부터 릴리스까지 파이프라인이 한층 견고해집니다.
처음에는 경고가 많아서 부담스러울 수 있지만, 하나씩 고쳐나가다 보면 어느새 Rust의 관용적 코드 스타일이 자연스러워질 거예요.
각 린트의 상세 설명과 예제는 Clippy 공식 린트 목록에서 확인할 수 있습니다.
This work is licensed under
CC BY 4.0