Rust 13. Build a Small CLI Project
요약
Rust 시리즈를 따라오며 설치, 디버깅, 기초 문법, ownership, module, testing, file I/O, serde까지 익혔다면 이제는 작은 결과물을 끝까지 한 번 묶어 보는 단계가 필요하다. 이 단계가 있어야 개별 개념이 흩어진 지식이 아니라 실제 작업 흐름으로 연결된다.
이 글은 word counter CLI를 예제로 잡아 프로젝트 구조, 파일 입력, 문자열 처리, HashMap, 테스트, 결과 출력이 한 프로그램 안에서 어떻게 연결되는지 정리한다. 결론부터 말하면 첫 mini project는 문제를 작게 잡고, 핵심 로직은 lib.rs, 입출력은 main.rs, 검증은 테스트로 나누는 구조가 가장 안정적이다.
문서 정보
- 작성일: 2026-04-15
- 검증 기준일: 2026-04-16
- 문서 성격: tutorial
- 테스트 환경: Windows 11 Pro, Windows PowerShell, Cargo CLI 예시
- 테스트 버전: rustc 1.94.0, cargo 1.94.0
- 출처 등급: 공식 문서만 사용했다.
- 비고: 이 글은 표준 라이브러리 기반 mini project 흐름에 집중하며,
clap같은 인자 파서나 고급 텍스트 전처리는 범위에서 제외한다.
문제 정의
Rust를 배우는 과정에서 흔한 문제는 “개념은 각각 알겠는데 하나의 프로그램으로 묶는 감각이 없다”는 점이다. 특히 초급자는 아래를 동시에 다루기 시작할 때 갑자기 난도가 올라간다고 느낀다.
- 커맨드라인에서 파일 경로 받기
- 파일 읽기
- 문자열을 가공해 단어 빈도 수 세기
- 결과 정렬과 출력
- 핵심 로직 테스트
이번 글의 목표는 새로운 문법을 추가하는 것이 아니라, 이미 배운 요소들을 하나의 작은 CLI로 연결하는 것이다.
확인된 사실
- 공식 문서 기준으로 Cargo package는 binary crate와 library crate를 함께 둘 수 있고, 재사용 로직은 library crate로 분리할 수 있다. 근거: Packages and Crates
- 표준 라이브러리 문서 기준으로
HashMap은 key-value 저장을 위한 표준 컬렉션이다. 근거: HashMap in std::collections - 표준 라이브러리 문서 기준으로
std::env::args와std::fs::read_to_string을 조합하면 가장 기본적인 파일 기반 CLI 입력 흐름을 만들 수 있다. 근거: std::env::args, std::fs::read_to_string - 공식 문서 기준으로 테스트는
#[cfg(test)]나tests/디렉터리에서 조직할 수 있다. 근거: Test Organization
초급용 mini project 구조는 아래 정도면 충분하다.
word-counter/
Cargo.toml
src/
main.rs
lib.rs
tests/
word_count.rs
src/lib.rs는 핵심 로직만 가진다.
use std::collections::HashMap;
pub fn count_words(text: &str) -> HashMap<String, usize> {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
let normalized = word.to_lowercase();
*counts.entry(normalized).or_insert(0) += 1;
}
counts
}
pub fn sort_counts(counts: HashMap<String, usize>) -> Vec<(String, usize)> {
let mut items: Vec<_> = counts.into_iter().collect();
items.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
items
}
src/main.rs는 입력과 출력만 조립한다.
use std::{env, error::Error, fs, io};
use word_counter::{count_words, sort_counts};
fn main() -> Result<(), Box<dyn Error>> {
let path = env::args().nth(1).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "usage: cargo run -- <file-path>")
})?;
let text = fs::read_to_string(&path)?;
let ranked = sort_counts(count_words(&text));
for (word, count) in ranked.into_iter().take(10) {
println!("{}\t{}", word, count);
}
Ok(())
}
tests/word_count.rs에는 공개 API 중심의 검증을 둘 수 있다.
use word_counter::{count_words, sort_counts};
#[test]
fn counts_words_case_insensitively() {
let counts = count_words("Rust rust RUST safety");
assert_eq!(counts.get("rust"), Some(&3));
assert_eq!(counts.get("safety"), Some(&1));
}
#[test]
fn sorts_by_frequency_descending() {
let sorted = sort_counts(count_words("b a a c c c"));
assert_eq!(sorted[0], ("c".to_string(), 3));
assert_eq!(sorted[1], ("a".to_string(), 2));
}
이 예제는 지금까지 배운 내용이 어떻게 이어지는지 잘 보여 준다.
env::args: 입력 경로 받기read_to_string: 파일 읽기split_whitespace,to_lowercase: 문자열 처리HashMap: 빈도 수 누적lib.rs와main.rs: 프로젝트 구조 분리tests/: 검증 흐름 추가
직접 확인한 결과
- 직접 확인한 결과: 현재 작성 환경에서 Rust toolchain 버전은 아래와 같았다.
rustc --version
cargo --version
- 관찰된 결과:
rustc 1.94.0 (4a4ef493e 2026-03-02)
cargo 1.94.0 (85eff7c80 2026-01-15)
- 직접 확인한 결과: 아래처럼
sample.txt를 두고 본문 mini project를 실행했을 때 출력은 아래와 같았다.
Rust rust safety safety safety tools
cargo run --quiet -- sample.txt
- 관찰된 결과:
safety 3
rust 2
tools 1
- 직접 확인한 결과: 같은 구조에서
cargo test --quiet를 실행했을 때 핵심 출력은 아래와 같았다.
cargo test --quiet
- 관찰된 결과:
running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
- 직접 확인 범위의 한계: 대표 입력 파일 기준의 실행과 테스트는 임시 Cargo 프로젝트에서 확인했지만, 큰 입력 파일, 구두점 처리, stop-word 제거 같은 확장 요구사항까지는 검증하지 않았다.
해석 / 의견
- 내 판단으로는 첫 mini project의 목적은 화려한 기능이 아니라 “개념들을 한 파일 구조로 묶는 경험”이다.
- 의견: 초급 단계에서는
clap,rayon, 복잡한 설정 파일까지 한 번에 넣기보다, 표준 라이브러리와 작은 pure function 조합으로 끝까지 완성하는 편이 더 도움이 된다. - 의견: 입력과 출력은 자주 바뀌지만 핵심 계산 로직은 비교적 안정적이므로, mini project일수록
lib.rs에 중심을 두는 편이 장기적으로 유리하다.
한계와 예외
- 이 예제는 공백 기준 토큰 분리만 사용하므로 구두점 처리, 형태소 분석, 유니코드 정규화 같은 고급 텍스트 처리는 포함하지 않았다.
- 큰 파일을 처리하거나 메모리 사용량이 중요한 경우에는 전부 읽기 대신 buffered reading이 필요할 수 있다.
- 실제 CLI 제품 수준으로 가면 옵션 파서, 출력 포맷, 에러 코드, 로그 처리 등을 더 설계해야 한다.
- 정렬 기준이나 stop-word 제거 여부는 문제 성격에 따라 달라질 수 있다.
댓글남기기