hyunsu blog

홈강좌Rust 실무 16편 중 9편째

Rust 를 잘 쓰는 법 1 — clippy 와 rustfmt 로 습관을 잡는다

목차 · 4
  1. 01다른 언어 습관으로 쓴 함수
  2. 02help 대로 고친 판
  3. 03rustfmt — 모양은 기계에 맡긴다
  4. 04실습 — 저장소에 넣는 두 줄

clippy 는 컴파일은 되지만 Rust 답지 않거나 실수하기 쉬운 코드를 짚어 주는 린터이고, rustfmt 는 코드 모양을 한 가지로 맞추는 포매터다. 둘 다 rustup 으로 Rust 를 깔 때 함께 깔린다. 다른 언어 습관으로 쓴 함수 하나에서 clippy 경고 다섯 개를 받고, help 대로 고쳐 깨끗하게 만든다.

01 다른 언어 습관으로 쓴 함수

점수 평균을 구하는 함수다. 컴파일도 되고 답도 맞다.

rust
fn average(scores: &Vec<i32>) -> f64 {
    if scores.len() == 0 {
        return 0.0;
    }
    let mut total = 0;
    for i in 0..scores.len() {
        total = total + scores[i];
    }
    return total as f64 / scores.len() as f64;
}

fn main() {
    let scores = vec![72, 85, 90];
    let copy = scores.clone();
    println!("{}", average(&copy));
}
bash
cargo clippy
text
warning: unneeded `return` statement
 --> src/main.rs:9:5
  |
9 |     return total as f64 / scores.len() as f64;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#needless_return
  = note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`
  |
9 -     return total as f64 / scores.len() as f64;
9 +     total as f64 / scores.len() as f64
  |

warning: writing `&Vec` instead of `&[_]` involves a new object where a slice will do
 --> src/main.rs:1:20
  |
1 | fn average(scores: &Vec<i32>) -> f64 {
  |                    ^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#ptr_arg
  = note: `#[warn(clippy::ptr_arg)]` on by default
help: change this to
  |
1 - fn average(scores: &Vec<i32>) -> f64 {
1 + fn average(scores: &[i32]) -> f64 {
  |

경고 다섯 개 가운데 앞의 둘이다. 읽는 법은 컴파일 오류와 같다(입문 2편의 무엇·어디·고치는 법).

  1. needless_return — 함수 끝의 return 은 필요 없다. 마지막 식이 반환값이다(입문 11편). help 가 -·+ 로 고친 줄을 보여 준다.
  2. ptr_arg — &Vec<i32> 보다 &[i32](슬라이스, 입문 21편)를 받으라는 것이다. 슬라이스로 받으면 Vec 도 배열도 넘길 수 있다.

나머지 셋은 이렇다.

text
warning: length comparison to zero
 --> src/main.rs:2:8
  |
2 |     if scores.len() == 0 {
  |        ^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `scores.is_empty()`
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#len_zero
  = note: `#[warn(clippy::len_zero)]` on by default
  1. len_zero — len() == 0 대신 is_empty().
  2. needless_range_loop — 번호로 도는 for i in 0..len() 대신 반복자(입문 14·37편).
  3. assign_op_pattern — total = total + x 대신 total += x.

#[warn(clippy::…)] 의 이름으로 문서를 찾으면 왜 그런지 설명과 예가 있다. 경고마다 help 의 링크가 그 문서다.

text
warning: `report` (bin "report") generated 5 warnings (run `cargo clippy --fix --bin "report" -p report -- ` to apply 3 suggestions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s

마지막 줄이 "5개 가운데 3개는 cargo clippy --fix 로 자동으로 고칠 수 있다" 고 알려 준다. 자동 고침은 편하지만, 처음에는 손으로 고치며 이유를 익히는 편이 낫다.

02 help 대로 고친 판

rust
fn average(scores: &[i32]) -> f64 {
    if scores.is_empty() {
        return 0.0;
    }
    let total: i32 = scores.iter().sum();
    total as f64 / scores.len() as f64
}

fn main() {
    let scores = vec![72, 85, 90];
    println!("{}", average(&scores));
}
text
    Checking report v0.1.0 (~/projects/report)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s

경고 없이 Finished 만 나왔다. 줄 수도 줄고 뜻도 더 잘 보인다. 1편에서 본 대로 반복자 판이 느리지도 않다.

03 rustfmt — 모양은 기계에 맡긴다

띄어쓰기·줄바꿈이 제멋대로인 파일에 cargo fmt --check 를 돌렸다. --check 는 고치지 않고 차이만 보여 준다.

bash
cargo fmt --check -- --color never
text
Diff in ~/projects/report/src/main.rs:1:
-fn average(scores:&[i32])->f64{
-    if scores.is_empty() { return 0.0; }
-  let total:i32=scores.iter().sum();
-    total as f64/scores.len() as f64
+fn average(scores: &[i32]) -> f64 {
+    if scores.is_empty() {
+        return 0.0;
+    }
+    let total: i32 = scores.iter().sum();
+    total as f64 / scores.len() as f64
 }
 
 fn main() {
Diff in ~/projects/report/src/main.rs:8:
-    let scores = vec![72,85,90];
+    let scores = vec![72, 85, 90];
     println!("{}", average(&scores));
 }
 

-- --color never 는 글에 싣느라 색을 끈 것이고, 터미널에서는 빨강·초록으로 보인다. - 줄이 지금 모양, + 줄이 rustfmt 의 모양이다. 차이가 있으면 종료 코드 1 로 끝난다. --check 없이 cargo fmt 를 치면 파일을 그 모양으로 바로 고친다.

모양을 두고 사람끼리 따질 일이 없어지고, 코드 리뷰의 차이(diff)에 모양 변경이 섞이지 않는다. 그래서 Rust 프로젝트는 거의 모두 rustfmt 의 기본 모양을 그대로 쓴다.

04 실습 — 저장소에 넣는 두 줄

CI(자동 검사)나 커밋 전에 이 두 줄을 돌리게 한다.

bash
cargo fmt --check
cargo clippy --all-targets -- -D warnings

--all-targets 는 테스트 코드까지, -D warnings 는 경고를 오류로 바꿔 하나라도 있으면 실패하게 한다. 11편에서 AI 에이전트에게 일을 맡길 때도 이 두 줄이 그대로 "다 됐는지" 를 판정하는 기준이 된다. 자기 프로젝트에 cargo clippy 를 한 번 돌려 몇 개가 나오는지 세어 보자. 다음 편은 clippy 가 다 잡지 못하는 설계 습관이다.