Rust로 CLI 도구 만들며 배운 소유권 시스템
배경
팀 내부에서 사용하는 배포 스크립트가 있었다. Node.js로 작성되어 있었는데, 파일 처리 과정에서 간헐적으로 메모리 이슈가 발생했다. Rust를 학습 중이던 차에 이 기회를 활용해 CLI 도구로 재작성하기로 했다.
소유권과의 첫 마주침
가장 먼저 마주한 문제는 파일 경로를 여러 함수에서 사용할 때였다.
fn read_config(path: String) -> Result<Config, Error> {
// path 소유권이 여기로 이동
}
fn validate_path(path: String) -> bool {
// path를 다시 사용하려 했으나 이미 이동됨
}
let config_path = String::from("config.toml");
read_config(config_path);
validate_path(config_path); // 컴파일 에러
해결은 참조자를 사용하는 것이었다.
fn read_config(path: &str) -> Result<Config, Error> {
// 빌림만 하고 소유권은 유지
}
fn validate_path(path: &str) -> bool {
Path::new(path).exists()
}
가변 참조의 제약
로그 수집기를 구현하면서 여러 곳에서 동일한 벡터에 접근해야 했다.
let mut logs = Vec::new();
let log_ref = &mut logs;
collect_errors(log_ref);
collect_warnings(&logs); // 불변 참조와 가변 참조 동시 사용 불가
이 경우 가변 참조 사용 후 스코프를 명확히 분리하거나, 필요시 Rc<RefCell<T>>를 고려해야 했다. 하지만 대부분은 함수 설계를 다시 하는 것으로 해결됐다.
얻은 것
Rust 컴파일러는 엄격하지만, 런타임 에러 대신 컴파일 타임에 문제를 잡아준다. 처음엔 답답했지만, 배포 후 안정성은 확실히 높아졌다. 바이너리 크기도 작고 실행 속도도 빨라져 만족스러운 결과였다.
다음엔 비동기 처리(tokio)를 붙여볼 예정이다.