Rust로 CLI 도구 만들어보며 배운 것들
배경
사내에서 사용하는 배포 자동화 스크립트가 Node.js로 작성되어 있었다. 문제는 새 팀원이 올 때마다 Node 버전 맞추고 의존성 설치하는 과정이 번거로웠다는 점. 바이너리 하나로 배포할 수 있다면 편할 것 같아서 Rust로 재작성을 시도했다.
기본 구조
clap 크레이트를 사용해 CLI 인자를 파싱했다. derive 매크로를 쓰니 보일러플레이트가 많이 줄었다.
use clap::Parser;
#[derive(Parser)]
#[clap(name = "deployer")]
struct Args {
#[clap(short, long)]
env: String,
#[clap(short, long)]
branch: Option<String>,
}
fn main() {
let args = Args::parse();
deploy(&args.env, args.branch.as_deref());
}
파일 처리
YAML 설정 파일을 읽어야 했는데, serde_yaml과 serde의 조합이 편했다. 하지만 에러 핸들링에서 Result 타입을 제대로 다루는 데 시간이 걸렸다.
use serde::Deserialize;
use std::fs;
#[derive(Deserialize)]
struct Config {
server: String,
path: String,
}
fn read_config(env: &str) -> Result<Config, Box<dyn std::error::Error>> {
let content = fs::read_to_string(format!("config/{}.yaml", env))?;
let config: Config = serde_yaml::from_str(&content)?;
Ok(config)
}
배운 점
소유권과 borrowing: 문자열을 여러 함수에 넘기면서 &str과 String의 차이를 체감했다. 처음엔 컴파일 에러가 답답했지만, 결국 메모리 안전성을 컴파일 타임에 보장받는다는 게 강점이었다.
에러 처리: ? 연산자가 편하긴 한데, 각 함수의 반환 타입을 명확히 해야 했다. anyhow 크레이트를 쓰면 더 간단해진다는 걸 나중에 알았다.
빌드 결과: 단일 바이너리로 빌드되니 배포가 정말 간단해졌다. 실행 속도도 Node 버전보다 체감상 빠르다.
결론
Rust가 가파른 학습 곡선을 가진 건 사실이지만, CLI 도구처럼 명확한 입출력이 있는 프로그램을 만들며 배우기엔 좋았다. 프로덕션에 쓰기엔 아직 이르지만, 사내 도구 정도는 충분히 실용적이다.