Chromium의 아주 작은 크레이트 권장 지침, 특히 모듈이나 소스 파일을 중첩하지 말라는 부분을 자세히 설명해 줄 수 있을까?
2010년쯤 함수를 최대한 작게 만들라는 유행이 떠오름. 당시 한두 줄짜리 함수와 불필요한 추상화가 겹겹이 쌓여 실제 동작을 파악하기 어려운 코드를 많이 검토했음. Chrome에도 나름의 이유가 있겠지만, 이 지침은 꽤 이상하게 느껴짐.
Cargo로 빌드하는 Rust에서는 크레이트마다 디렉터리, Cargo.toml, src/ 등이 필요해 추상화에 따른 부대 비용이 큼. 반면 내가 써 본 Google 빌드 시스템에서는 같은 디렉터리에 여러 언어의 소스 파일을 섞어 두고, 필요에 따라 라이브러리를 잘게 나눔. Chromium도 비슷해 보임.
Rust의 빌드 병렬화 단위는 크레이트이므로, 인지적 부담만 크지 않다면 크레이트를 늘리는 이점이 있음. 일반적인 Rust 프로젝트에서 모듈을 나누듯 크레이트를 나눈다고 생각할 수 있음.
코드 검색에서는 아직 Rust가 많지 않았지만, https://source.chromium.org/chromium/chromium/src/+/main:tes...에서는 Rust 파일 약 4개가 각각 별도 크레이트로 빌드되는 듯함. 그중 하나는 절차적 매크로를 정의하므로 Cargo 프로젝트에서도 별도 크레이트가 필요할 것 같음.
컴파일 시간 최적화 때문일 수 있음. 크레이트가 컴파일 단위이므로 작게 나누면 무관한 코드를 대량으로 다시 컴파일할 가능성이 줄어듦.
작은 크레이트와 중첩 없는 모듈 구조는 코딩 에이전트가 문맥 창을 과도하게 쓰지 않고 코드를 검색하는 데도 도움이 될 듯함.
크레이트의 크기와 추상화 설계는 별개임. 필요한 만큼 모듈과 타입을 만들고 함수도 충분히 크게 작성하되, 모듈을 하나의 큰 크레이트 대신 여러 크레이트로 나누라는 뜻임. 트레이트 일관성 규칙도 명시하면서, 작은 크레이트를 고집하다 설계가 어색해지면 라이브러리를 키워도 된다고 함.
작은 크레이트는 링크와 의존성 그래프를 더 세밀하게 나눌 수 있어 재빌드에 특히 유리함. 이전 직장의 거대한 공용 유틸리티 크레이트에서는 IP 주소 기능만 필요해도 무선 주파수 기능까지 따라왔고, 한 하위 모듈만 갱신해도 버전 변경이 모든 의존 프로젝트로 퍼졌음. net-utils와 rf-utils로 나눴다면 이런 문제가 덜했을 듯함.
한두 줄짜리 함수와 크레이트 전체 소스 파일은 규모가 전혀 다르므로, 전자의 문제가 후자에도 적용되지는 않음. 지침도 작은 크레이트를 선호하라는 것이지 항상 극도로 작게 만들라는 뜻은 아니며, 뒤에서 큰 크레이트도 허용한다고 명시함.
근거 역시 거대한 코드베이스, 의존성 추가가 빌드 그래프의 다른 영역에 미치는 영향, 병렬 컴파일과 빌드 캐시 성능 개선이라고 적혀 있음.
Google이라면 모든 코드를 거대한 모노레포에 넣고 Blaze로 빌드할 것이라 추측함. 컴파일 단위가 크레이트라면 최대한 작은 크레이트를 선호하는 것도 이해됨.
이 지침을 LLM에 그대로 넣고 엄격히 따르라고 하면 얼마나 유용할까?
전혀 유용하지 않음. 문맥만 소모하며, 나중에 LLM이 코드를 읽을 수 있도록 문맥 공간을 최대한 확보해야 함.
타입 이름을 가져와 한정자 없이 쓰라는 규칙은 io::Error 대신 IoError처럼 타입 이름에 중복 정보를 넣도록 유도할 뿐임. HashMap처럼 널리 알려진 타입이 아니라면 문맥을 알 수 있도록 경로를 명시하는 편을 선호함. Source 같은 일반적인 이름만 보고 무엇인지 어떻게 알 수 있을까? 코드를 이해하려고 가져오기 선언을 찾아보거나 LSP에 의존할 필요는 없어야 함. 가져오기 선언을 줄이면 불필요한 병합 충돌도 줄어듦.
바로 아래에 모호한 경우에는 io::Error처럼 쓰라고 되어 있음. 구체적으로는 regex::Regex 대신 Regex를 쓰라는 것이므로 더 합리적으로 보임.
어느 쪽이든 일률적인 규칙은 좋지 않다고 봄. 경로를 명시하는 편이 나을 때도 있고, 생략하는 편이 더 명확할 때도 있음.
Google과 Meta의 Rust 프로젝트를 살펴보며 LLM으로 스타일을 추출하고 따라 해 왔으며, fmt와 clippy 설정도 도움이 됨.
하지만 프로젝트마다 차이가 있어 이 가이드만큼 일관되고 신뢰할 만하지는 않았음.
확장 트레이트에 관해 잘 알려지지 않은 팁이 있음. 트레이트 위에 #[rust_analyzer::prefer_underscore_import] 를 붙이면, RustRover가 아닌 rust-analyzer에서 자동 완성과 가져오기 보조 기능이 기본적으로 트레이트를 as _ 로 가져옴. 확장 트레이트에 아주 유용함.
Chromium에서 async / .await를 쓰지 말라는 지침은 꽤 단호함. 기억이 맞다면 Rust for Linux 쪽에서도 비공식적으로 비슷한 방침을 취함.
대부분 Rust 생태계의 표준적인 관행이지만, 한곳에 정리되어 있다는 것만으로도 매우 유용함.
라이브러리 코드와 애플리케이션 코드를 구분한 점이 특히 마음에 듦. Rust는 복잡한 제네릭 코드 때문에 지나치게 나쁜 평가를 받지만, 일반적인 애플리케이션에서는 그 정도로 복잡하거나 범용적인 코드를 작성할 일이 드묾.
지금도 C나 C++로 새 코드를 작성하는 곳이 있을까? 대기업에서도 서서히 퇴장하는 언어처럼 보임.
Windows, Linux, Chrome, gcc, LLVM, AAA 게임 엔진 같은 대형 프로젝트에서는 여전히 새 C·C++ 코드를 대량으로 작성할 것임. 임베디드 분야도 거의 마찬가지임.
개인 개발자로서 나 역시 C와 C++를 선호함. 특히 작업 흐름에 LLM을 도입하면서 C의 단순함에 더 끌리게 됨. 직접 작성하는 코드가 줄어들면 C++의 여러 편의 기능도 굳이 필요하지 않음.
전혀 그렇지 않음. Rust가 C++ 코드를 매끄럽게 읽을 수 있게 되기 전까지는 당분간 Rust보다 C++로 작성되는 코드가 더 많을 것임. 고성능 컴퓨팅, 금융, AI, 게임, 데이터베이스, 하드웨어 분야에서 계속 C++를 쓰고 있음.
내게는 안타깝지만 여전히 쓰고 있음. 일부 기업은 위험 회피 성향이 강해 익숙한 문제를 안고 가는 편을 택하며, 그 대가로 무엇을 포기하는지도 모름.
내가 다니는 대기업에서도 새 C++ 코드를 많이 작성하고 있음.
메모리 관리를 세심하게 최적화해야 하는 고성능 컴퓨팅에서는 C/C++가 여전히 유효함.
불리언 대신 열거형을 쓰라는 규칙은 혼자 개발할 때도 지킬 것임. 인증서 검증기에서 플래그를 반대로 쓰는 실수를 한 번 막아 준 적이 있음.
이는 더 일반적인 원칙인 해석의 여지를 좁히기에 해당함. 참이나 거짓은 무수히 많은 것을 뜻할 수 있지만, Rust 열거형의 특정 변형은 하나의 의미로 한정됨.