파일 기반 모듈 분리
커지는 모듈을 별도 파일과 디렉터리로 분리하면서 mod 선언과 모듈 트리 경로가 유지되는 방식을 익힙니다.
이번 장의 예제들은 하나의 파일에 여러 모듈을 정의했습니다.
하지만 모듈이 커지면 정의를 여러 파일로 분리해 코드를 더 쉽게 찾아갈 수 있어야 합니다.
예를 들어 여러 개의 레스토랑 관련 모듈을 가지고 있는 예제 6-17 코드로 시작해봅시다.
크레이트 루트 파일에 모든 모듈이 정의되는 형태 대신 이 모듈들을 파일로 추출해보겠습니다.
이 경우 크레이트 루트 파일은 src/lib.rs지만, 이러한 절차는 크레이트 루트 파일이 src/main.rs인 바이너리 크레이트에서도 작동합니다.
먼저 front_of_house 모듈을 파일로 추출하겠습니다.
front_of_house 모듈에 대한 중괄호 내부의 코드를 지우고 mod front_of_house; 선언 부분만 남겨서, src/lib.rs가 예제 6-21의 코드만 있도록 해봅시다.
예제 6-22의 src/front_of_house.rs 파일을 만들기 전까지는 컴파일되지 않음을 유의하세요.
예제 6-21: 본문이 src/front_of_house.rs에 위치할front_of_house 모듈 선언하기
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
다음으로 예제 6-22처럼 src/front_of_house.rs이라는 새 파일을 만들어 중괄호 안에 있던 코드를 위치시킵니다.
크레이트 루트에 front_of_house라는 이름의 모듈 선언이 나왔으므로 컴파일러는 이 파일을 살펴봐야 한다는 것을 알게 됩니다.
front_of_house 모듈 본문 정의하기
pub mod hosting {
pub fn add_to_waitlist() {}
}
모듈 트리에서 mod 선언을 사용하여 파일을 로드하는 것은 한 번만 하면 됩니다.
일단 그 파일이 프로젝트의 일부란 것을 컴파일러가 파악하면(그래서 모듈 트리 내 mod 구문을 집어넣은 곳 옆에 코드가 있음을 알게 되면), ‘경로를 사용하여 모듈 트리의 아이템 참조하기’절에서 다루었던 것처럼 프로젝트의 다른 파일들은 선언된 위치의 경로를 사용하여 로드된 파일의 코드를 참조해야 합니다.
즉, mod는 다른 프로그래밍 언어에서 볼 수 있는 ‘포함하기(include)’ 연산이 아닙니다.
다음으로 hosting 모듈을 파일로 추출하겠습니다.
hosting이 루트 모듈이 아니라 front_of_house의 자식 모듈이기 때문에 과정이 약간 다릅니다.
hosting의 파일을 모듈 트리 내 부모의 이름이 될 새 디렉터리, 즉 이 경우에는 src/front_of_house/ 에 위치시키겠습니다.
hosting을 옮기는 작업을 시작하기 위하여, src/front_of_house.rs에는
hosting 모듈의 선언만 있도록 수정합니다.
pub mod hosting;
그다음 src/front_of_house 디렉터리를 만들고 그 안에 hosting.rs
파일을 생성한 다음 hosting 모듈 내용을 작성합니다.
pub fn add_to_waitlist() {}
hosting.rs를 src/front_of_house 대신 src 디렉터리에 두는 것만으로는 루트 모듈이 되지 않습니다. 현재 mod hosting; 선언은 front_of_house 안에 있으므로 컴파일러는 그 자식 경로에서 파일을 찾습니다. 루트의 src/hosting.rs를 모듈로 만들려면 크레이트 루트에 별도의 mod hosting; 선언이 필요합니다.
어떤 파일에서 어떤 모듈의 코드를 확인할지에 대한 컴파일러의 규칙은 디렉터리와 파일이 모듈 트리와 더 밀접하게 일치한다는 것을 의미합니다.
대체 파일 경로
지금까지는 러스트 컴파일러가 사용하는 가장 관용적인 파일 경로를 다루었지만, 러스트는 예전 스타일의 파일 경로 또한 지원합니다. 크레이트 루트 내에 선언된
front_of_house모듈에 대하여, 컴파일러는 다음의 위치에서 모듈의 코드를 찾아볼 것입니다.
- src/front_of_house.rs (우리가 지금 다룬 형태)
- src/front_of_house/mod.rs (예전 스타일, 여전히 지원되는 경로)
front_of_house의 서브모듈인hosting이라는 모듈에 대해서는 다음의 위치에서 모듈의 코드를 찾아볼 것입니다.
- src/front_of_house/hosting.rs (우리가 지금 다룬 형태)
- src/front_of_house/hosting/mod.rs (예전 스타일, 여전히 지원되는 경로)
만약 같은 모듈에 대해 두 스타일 모두를 사용하면 컴파일 에러가 납니다. 같은 프로젝트에서 서로 다른 모듈에 대해 양쪽 스타일을 섞어 사용하는 것은 허용되지만, 프로젝트를 살펴보는 사람들에게 혼란을 줄 가능성이 있습니다.
mod.rs라는 이름의 파일을 사용하는 스타일의 주요 단점은 프로젝트에 여러 파일의 이름이 mod.rs로 끝나게 되어, 에디터에서 이 파일들을 동시에 열어두었을 때 헷갈릴 수 있다는 점입니다.
각 모듈의 코드를 별도의 파일로 옮겼고, 모듈 트리는 동일한 상태로 남아있습니다.
eat_at_restaurant 내의 함수 호출은 그 정의가 다른 파일들에 있다 하더라도 아무 수정 없이 동작할 것입니다.
이러한 기술은 모듈의 크기가 증가했을 때 이를 새로운 파일로 옮기도록 해줍니다.
src/lib.rs 파일의 pub use crate::front_of_house::hosting 구문을 변경하지 않았으며, use 구문이 크레이트의 일부로 컴파일되는 파일에 영향을 주지 않는다는 점도 주목해 주세요.
본문 없는 mod는 모듈을 선언하고 관용적인 파일 경로에서 본문을 찾습니다. #[path = "..."] 속성을 사용하면 이 기본 탐색 경로를 명시적으로 바꿀 수도 있습니다. 인라인 모듈 블록 밖에서 이 속성의 상대 경로는 선언이 들어 있는 소스 파일의 디렉터리를 기준으로 해석됩니다. 인라인 모듈 블록 안에서는 그 인라인 모듈 경로가 기준에 붙고, 선언을 담은 파일이 lib.rs, main.rs, mod.rs가 아닌 경우에는 그 파일의 모듈 이름도 디렉터리 경로에 붙습니다.
반면 use는 이미 선언된 아이템의 이름을 현재 스코프에 바인딩하고, pub use는 그 이름을 새 공개 경로로 다시 내보냅니다. 일반 경로로 공개 아이템에 접근하려면 호출 위치에서 그 아이템까지의 모든 조상 모듈에 접근할 수 있어야 합니다. pub use로 만든 공개 경로는 공개 아이템의 비공개 정의 경로를 우회할 수 있지만, 대상 아이템 자체의 가시성보다 더 넓게 다시 내보낼 수는 없습니다.
모듈 요약
러스트에서는 패키지를 여러 크레이트로 나누고, 크레이트를 여러 모듈로 나누어 한 모듈에 정의된 아이템을 다른 모듈에서 참조할 수 있게 해줍니다.
절대 경로나 상대 경로를 지정하면 이를 수행할 수 있습니다.
이러한 경로는 use 구문을 사용해 스코프 안으로 가져올 수 있으므로 해당 스코프에 있는 아이템을 여러 번 사용해야 할 때 더 짧은 경로를 사용할 수 있습니다.
모듈 코드는 기본적으로 비공개지만, pub 키워드를 추가해 정의를 공개할 수 있습니다. 비공개 아이템은 그 아이템이 정의된 모듈과 그 자손 모듈에서 접근할 수 있으므로, 파일을 분리했다는 이유만으로 모든 모듈과 아이템을 공개할 필요는 없습니다.
다음 장에서는 이렇게 깔끔하게 구성된 여러분의 코드에서 사용할 수 있는 표준 라이브러리의 컬렉션 자료구조를 몇 가지 살펴보겠습니다.
모듈을 파일로 나눌 때는 모듈 트리, 파일 탐색, 스코프 이름, 공개 API를 서로 다른 계약으로 점검해야 합니다. 아래 다이어그램은 mod, 관용 경로와 #[path], use, pub use의 역할을 하나의 흐름으로 정리합니다.
MOD TREE · FILE LOOKUP · PUBLIC API
mod가 트리를 만들고, 파일은 그 모듈의 본문을 담는다
모듈의 정체성은 선언 위치가 정하고 파일 위치는 관용적인 본문 탐색 규칙을 따릅니다. use는 이름을 바인딩하고 pub use는 공개 경로를 설계하므로 파일 분리와 공개 API 변경은 별개입니다.
1 · 부모가 자식 모듈을 선언
// src/lib.rs
mod front_of_house;
크레이트 루트의 선언이 crate::front_of_house 노드를 만듭니다. 파일을 디렉터리에 놓는 것만으로 모듈이 생기지는 않습니다.
2 · 그 모듈이 다시 자식을 선언
// src/front_of_house.rs
pub mod hosting;
선언 위치 때문에 hosting은 루트가 아니라 front_of_house의 자식입니다. pub는 이름의 가시성을 정할 뿐 파일 탐색 규칙을 바꾸지 않습니다.
| 구문과 위치 | 만드는 계약과 지켜야 할 경계 |
|---|---|
| 루트의 부모 모듈 선언 |
|
| 부모 안의 자식 모듈 선언 |
|
| 사용자 지정 파일 경로 | 이 예제는 |
| 현재 스코프의 이름 바인딩 | 이미 선언된 아이템을 현재 스코프의 짧은 이름으로 바인딩합니다. 어떤 파일이 크레이트에 포함되는지 결정하거나 새 모듈을 선언하지 않습니다. |
| 공개 이름 재내보내기 |
|
-
루트 선언이 부모 모듈을 만든다
mod front_of_house;는 논리 노드를 만들고 현대식front_of_house.rs또는 예전식front_of_house/mod.rs에서 본문을 찾습니다. 둘을 함께 둘 수는 없습니다. -
부모 선언이 자식 경로를 정한다
부모 안의
mod hosting;때문에 본문 후보는front_of_house/hosting.rs또는front_of_house/hosting/mod.rs입니다. 파일 이동만으로 루트 모듈이 되지 않습니다. -
파일 탐색 경로를 명시적으로 바꾼다
src/lib.rs의#[path = "front.rs"]는src/front.rs를 찾습니다. 인라인 모듈 밖에서는 선언 소스의 디렉터리가 기준이며, 인라인 모듈 안에서는 인라인 경로와 소스 파일 형식에 따른 경로가 더 붙습니다. 논리 트리는 여전히mod선언이 정합니다. -
use는 현재 스코프의 이름이다이미 선언된 아이템을 짧게 부를 바인딩이며 파일을 로드하거나 새 모듈을 만들지 않습니다.
-
pub use는 사용자가 볼 경로다pub use가 놓인 모듈까지 접근 가능한 코드에 새 경로를 제공합니다. 공개 아이템의 비공개 정의 경로를 우회할 수 있지만 대상 자체의 가시성보다 더 넓게 다시 내보내지는 못합니다.
점검 순서: 선언한 부모 스코프 → 논리 모듈 경로 → 관용 파일 후보 또는 #[path] → 아이템 가시성 → use와 pub use의 사용자 경로. 일반 경로는 대상까지의 모든 조상 모듈이 접근 가능해야 하며, 비공개 아이템은 정의 모듈과 그 자손에서 접근할 수 있습니다. 파일 분리만을 이유로 모두 pub로 만들 필요는 없습니다.