본문으로 건너뛰기

안동민 개발노트

본문 시작

파일 기반 모듈 분리

커지는 모듈을 별도 파일과 디렉터리로 분리하면서 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 모듈 선언하기
src/lib.rs
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라는 이름의 모듈 선언이 나왔으므로 컴파일러는 이 파일을 살펴봐야 한다는 것을 알게 됩니다.

예제 6-22: src/front_of_house.rs 파일에 front_of_house 모듈 본문 정의하기
src/front_of_house.rs
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 모듈의 선언만 있도록 수정합니다.

src/front_of_house.rs
pub mod hosting;

그다음 src/front_of_house 디렉터리를 만들고 그 안에 hosting.rs 파일을 생성한 다음 hosting 모듈 내용을 작성합니다.

src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}

hosting.rssrc/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 선언이 논리 모듈 트리와 파일 탐색을 연결하고 use와 pub use가 각각 스코프 이름과 공개 API를 설계하는 경계를 정리한 표

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는 이름의 가시성을 정할 뿐 파일 탐색 규칙을 바꾸지 않습니다.

선언에서 모듈 트리, 본문 파일, 스코프 이름, 공개 API로 이어지는 계약
구문과 위치 만드는 계약과 지켜야 할 경계
루트의 부모 모듈 선언
mod front_of_house;

crate::front_of_house를 선언하고 관용적으로 src/front_of_house.rs 또는 예전 형식인 src/front_of_house/mod.rs에서 본문을 찾습니다. 같은 모듈에 두 파일을 함께 두면 오류입니다.

부모 안의 자식 모듈 선언
mod hosting;

hosting을 부모의 자식으로 선언하고 src/front_of_house/hosting.rs 또는 src/front_of_house/hosting/mod.rs를 찾습니다. src/hosting.rs로 옮기기만 해서는 루트 모듈이 되지 않습니다.

사용자 지정 파일 경로
// src/lib.rs
#[path = "front.rs"]
mod front_of_house;

이 예제는 src/front.rs를 찾습니다. 인라인 모듈 밖에서는 선언 소스의 디렉터리가 기준입니다. 인라인 모듈 안에서는 인라인 경로가 붙고, lib.rs·main.rs·mod.rs가 아닌 소스라면 그 파일의 모듈 이름도 디렉터리로 붙습니다. 파일 경로를 바꿔도 논리 모듈 이름과 트리 위치는 선언이 정합니다.

현재 스코프의 이름 바인딩
use module::item;

이미 선언된 아이템을 현재 스코프의 짧은 이름으로 바인딩합니다. 어떤 파일이 크레이트에 포함되는지 결정하거나 새 모듈을 선언하지 않습니다.

공개 이름 재내보내기
pub use module::item;

pub use가 놓인 모듈까지 접근 가능한 코드에 새 경로를 제공합니다. 공개 아이템의 비공개 정의 경로는 이 새 경로로 우회할 수 있지만, 대상 자체의 가시성보다 더 넓게 다시 내보낼 수는 없습니다.

  1. 루트 선언이 부모 모듈을 만든다

    mod front_of_house;는 논리 노드를 만들고 현대식 front_of_house.rs 또는 예전식 front_of_house/mod.rs에서 본문을 찾습니다. 둘을 함께 둘 수는 없습니다.

  2. 부모 선언이 자식 경로를 정한다

    부모 안의 mod hosting; 때문에 본문 후보는 front_of_house/hosting.rs 또는 front_of_house/hosting/mod.rs입니다. 파일 이동만으로 루트 모듈이 되지 않습니다.

  3. 파일 탐색 경로를 명시적으로 바꾼다

    src/lib.rs#[path = "front.rs"]src/front.rs를 찾습니다. 인라인 모듈 밖에서는 선언 소스의 디렉터리가 기준이며, 인라인 모듈 안에서는 인라인 경로와 소스 파일 형식에 따른 경로가 더 붙습니다. 논리 트리는 여전히 mod 선언이 정합니다.

  4. use는 현재 스코프의 이름이다

    이미 선언된 아이템을 짧게 부를 바인딩이며 파일을 로드하거나 새 모듈을 만들지 않습니다.

  5. pub use는 사용자가 볼 경로다

    pub use가 놓인 모듈까지 접근 가능한 코드에 새 경로를 제공합니다. 공개 아이템의 비공개 정의 경로를 우회할 수 있지만 대상 자체의 가시성보다 더 넓게 다시 내보내지는 못합니다.

점검 순서: 선언한 부모 스코프 → 논리 모듈 경로 → 관용 파일 후보 또는 #[path] → 아이템 가시성 → usepub use의 사용자 경로. 일반 경로는 대상까지의 모든 조상 모듈이 접근 가능해야 하며, 비공개 아이템은 정의 모듈과 그 자손에서 접근할 수 있습니다. 파일 분리만을 이유로 모두 pub로 만들 필요는 없습니다.

이 페이지의 목차