Rust의 타입 시스템으로 런타임 에러 줄이기
배경
사내 데이터 파이프라인 API를 Node.js로 운영하던 중 null/undefined 관련 런타임 에러가 주기적으로 발생했다. TypeScript를 쓰고 있었지만, 외부 API 응답이나 DB 쿼리 결과를 다룰 때 타입 가드가 누락되면 프로덕션에서 터지는 일이 잦았다.
성능 이슈도 있어서 일부 모듈을 Rust로 재작성하기로 했다.
Option 타입으로 null 처리
Rust는 null이 없다. 대신 Option<T> 타입으로 값의 존재 여부를 명시적으로 표현한다.
fn find_user(id: u32) -> Option<User> {
// DB 조회 로직
if found {
Some(user)
} else {
None
}
}
let user = find_user(123);
match user {
Some(u) => println!("User: {}", u.name),
None => println!("Not found"),
}
컴파일러가 None 케이스 처리를 강제하기 때문에 잊어버릴 수가 없다.
Result 타입으로 에러 전파
에러 처리도 Result<T, E> 타입으로 명시적이다.
fn parse_config(path: &str) -> Result<Config, std::io::Error> {
let content = std::fs::read_to_string(path)?;
Ok(serde_json::from_str(&content)?)
}
? 연산자로 에러를 상위로 전파하거나, match로 직접 처리할 수 있다. 예외를 던지는 게 아니라 반환값으로 다루니 제어 흐름이 명확했다.
실제 효과
3개월간 운영하면서 타입 관련 런타임 에러가 0건이었다. 컴파일만 통과하면 기본적인 타입 안정성은 보장됐다. 물론 비즈니스 로직 버그는 별개지만, 적어도 "Cannot read property of undefined" 같은 에러는 사라졌다.
러닝커브가 있지만, 한번 익숙해지니 타입 시스템이 안전망 역할을 확실히 해줬다.