Lobsters

Google's Rust Style Guide

Google의 Rust 스타일 가이드

Google의 Rust 스타일 가이드는 포맷뿐 아니라 API 설계와 코드 구조까지 다룹니다. 대규모 코드 변경과 장기적인 유지보수를 고려해 일관된 관례를 제시하며, 라이브러리 코드와 애플리케이션 코드에 서로 다른 수준의 권고를 적용합니다.

에디터 노트

crate를 작게 나누라는 권고는 보편적인 Rust 관례라기보다 빌드 그래프와 도구가 큰 코드베이스에서 맡는 역할에 달려 있습니다. 가이드도 이를 Chromium의 구조로 한정하고, Cargo를 쓰는 third-party 코드에는 일반적인 디렉터리 구성을 허용합니다. Lobsters 댓글에서는 Cargo 프로젝트의 crate 규모에 이견이 나왔고, 작은 crate가 증분 빌드 단위로 유리할 수 있다는 설명도 이어졌습니다. 따라서 이 쟁점은 코드 조직 취향만으로 정리하기 어렵고, 사용하는 빌드 시스템의 캐시와 의존성 관리가 근거가 됩니다. 반면 borrowing 우선, API의 오류 의미 구분, 읽기 어려운 조합 체인을 풀어 쓰라는 지침은 빌드 환경에 덜 묶인 설계 원칙으로 제시됩니다.

AI 요약

Google은 google3, Android, ChromeOS, Fuchsia, Chrome 등 여러 코드베이스에서 Rust를 사용합니다. 이 가이드는 코드의 일관성과 가독성을 높이고, 대규모 코드 변경(Large-Scale Changes, LSC)을 도구로 수행하기 쉽게 만드는 것을 목표로 합니다. 공개 Rust 스타일 규칙과 충돌하지 않는 한 이를 따르며, 규칙은 장기적인 유지보수에 도움이 되는지와 개발자·리뷰어에게 추가 부담을 줄 만큼 실익이 있는지를 기준으로 삼습니다.

코드 구조와 가져오기

Chromium에서는 작은 crate를 선호하며, 가능하면 하나의 추상화를 담은 단일 파일을 권합니다. 한 crate에 속한 Rust 파일은 한 디렉터리에 두고, 파일을 하위 디렉터리로 나누고 싶을 만큼 커졌다면 crate 분할을 고려합니다. 다만 이 구조는 Chromium 빌드 방식에 맞춘 것으로, Cargo를 함께 사용하는 //third_party 코드는 일반적인 Cargo 디렉터리 구조를 따릅니다. 모듈은 정의보다 모듈 선언과 재수출을 중심으로 구성하고, glob import는 예외적인 경우에만 허용합니다. 함수는 모듈을 가져온 뒤 모듈명::함수명()으로 호출하는 방식을 기본으로 권합니다.

API와 소유권

API 설계 규칙은 라이브러리 코드에 구속력을 갖고, 애플리케이션 코드에는 강한 권고로 적용합니다. 재사용 가능한 라이브러리는 구체 타입보다 유연한 인터페이스를 우선해, 입력을 받을 때 iterator나 impl IntoIterator를 고려하고 반환할 때도 Vec<T>보다 iterator를 고려합니다. 반면 애플리케이션 코드는 성능상 이점이나 유연성이 분명하지 않다면 단순한 slice와 Vec를 선택해도 됩니다. 매개변수의 의미가 불분명하면 bool 대신 enum을 쓰고, 의미가 다른 값은 type alias보다 newtype으로 구분합니다. 튜플보다 이름 있는 필드를 권하며, 공개 API의 enum과 공개 필드가 있는 struct에는 팀 간 호환성을 위해 #[non_exhaustive]를 고려합니다.

메모리 관리에서는 Rc와 Arc보다 단독 소유권과 borrowing을 우선합니다. 불필요한 깊은 복사를 피하고, 함수는 &Vec<T>보다 &[T], &String보다 &str처럼 내부 데이터에 직접 참조를 받도록 설계합니다. 오류 처리는 실패 원인이 명백하면 Option, 호출자가 원인을 알아야 복구할 수 있으면 Result, 코드 버그나 불변식 위반이면 panic!을 사용합니다. 가능한 경우 derive로 trait을 구현하고, 기능을 타입에 붙이기 위해서만 trait을 만들지는 않습니다. 라이브러리에서는 dyn Trait보다 generic이나 impl Trait을 우선합니다.

구현과 읽기 쉬운 코드

안전한 코드로 구현할 수 있다면 unsafe를 피하고, 불가피한 unsafe 블록에는 정확성을 설명하는 SAFETY 주석을 답니다. 복잡한 iterator 조합이나 외부 상태를 변경하는 체인은 for 루프로 풀어 쓰며, 짧고 의도가 분명한 any() 같은 조합은 그대로 사용합니다. 불필요한 panic 가능성을 줄이고, panic이 필요하면 unwrap보다 이유를 적는 expect를 권합니다. 공개 API에는 문서 주석을 달고, panic 조건과 unsafe 코드의 불변식을 설명합니다. Chromium에서는 async/.await 대신 sequence를 선호하며, Fuchsia에서는 std::sync나 parking_lot 대신 fuchsia_sync::Mutex를 사용합니다.

Lobsters 반응

  • @novedevo — 제 Rust 코드는 꽤 스타일이 좋다고 생각하지만, 여기에는 적용할 만한 권고가 분명히 있습니다. 예를 들어 for 루프가 더 읽기 쉬운데도 복잡한 iterator 체인을 쓰곤 하고, 루프 헤더에 iterator 조합자를 넣기도 합니다. 제 안에는 point-free Haskeller가 있는 것 같습니다. 다만 Google의 crate 크기 권고에는 동의하지 않습니다. Google 내부 빌드 도구에서는 더 편할지 몰라도 Cargo를 쓴다면 복잡성이 정당한 경우 crate 하나에 .rs 파일을 몇 개보다 더 넣고 싶습니다. 공정하게 말하면 이 부분은 Chrome에만 적용되는 듯하고, 그 빌드 시스템은 잘 모릅니다.
    • @siru — 오래된 생각일 수 있지만 crate 경계가 많으면 incremental build가 더 잘 작동한다고 알고 있었습니다. 작은 crate가 많으면 다시 컴파일할 필요가 없는 crate가 생기지만, 큰 crate 하나라면 전부 다시 컴파일해야 한다는 뜻입니다.
    • @refi64 — GN에도 해당하는지는 모르겠습니다. 다만 Rust용 외부 빌드 시스템 중에는 한 crate의 incremental build 캐시를 다음 빌드에서 재사용하지 못하는 경우가 많다고 알고 있습니다. 그러면 incremental build의 단위는 crate뿐입니다.
  • @joshka — Microsoft의 가이드(https://microsoft.github.io/rust-guidelines/)와 Ed Page의 가이드(https://epage.github.io/dev/rust-style/)도 있습니다.
  • @citbl — Go 스타일 가이드도 있나요? 아니면 Go에는 필요하지 않나요?
    • @mond — 있습니다. 여기일 수 있습니다: https://google.github.io/styleguide/go/index. 흥미롭게도 Google의 Python 및 C++ 스타일 가이드와 달리 줄 길이 제한은 없습니다.
    • @fzakaria — 그 부분은 go fmt가 처리하는 것 같네요.

원문: Google / 번역·요약: Trawling