모듈 스코프와 공개 범위
mod로 관련 코드를 모듈 트리에 묶고 기본 비공개 규칙을 바탕으로 아이템의 스코프와 공개 경계를 설계합니다.
이 글부터는 모듈과, 아이템에 이름을 붙이는 경로(path), 경로를 스코프로 가져오는 use 키워드, 아이템을 공개하는 pub 키워드를 차례로 다룹니다. as 키워드와 외부 패키지, 글롭 연산자도 뒤에서 봅니다. 먼저 나중에 코드를 정리할 때 다시 찾아볼 수 있도록 모듈의 규칙을 한곳에 모아 정리하고, 그 규칙을 작은 예제로 확인한 뒤, 모듈로 코드를 묶는 방법을 살펴봅니다.
모듈 치트 시트
아래 표는 크레이트 루트에서 mod 선언을 읽어 모듈 트리를 만드는 과정을 정리한 것입니다.
MODULE DECLARATION · SOURCE DISCOVERY
파일을 만든 것만으로는 모듈 트리가 생기지 않는다
각 크레이트는 하나의 루트 소스 파일에서 시작합니다. 부모의 mod 선언이 새 모듈을 트리에 넣고, 선언 형태가 본문을 인라인에서 읽을지 외부 파일에서 읽을지 결정합니다.
-
크레이트 루트에서 시작
rustc는 크레이트마다 하나의 루트 소스를 입력받습니다. Cargo 기본 배치의src/lib.rs와src/main.rs는 각각 별도 library·binary 크레이트의 루트이며, target의path로 바꿀 수 있습니다. -
부모가 모듈을 선언
mod garden { /* ... */ }또는mod garden;이 논리적인crate::garden모듈을 만듭니다. 디렉터리나 파일이 존재하는 것만으로는 모듈이 되지 않습니다. -
선언 형태에 따라 본문 탐색
중괄호가 있으면 본문은 같은 소스에 인라인으로 있습니다. 세미콜론 선언은 기본적으로
garden.rs또는garden/mod.rs중 한 외부 파일을 사용하며, 둘을 동시에 둘 수 없습니다. -
본문이 모듈 트리에 합류
선택된 본문의 아이템과 서브모듈이
crate::garden::…경로 아래에 놓입니다. 소스 파일 경로는 본문 탐색 규칙이고, 코드에서 쓰는 모듈 경로는 논리적인 이름 경로입니다.
| 선언과 모듈 본문 위치 | 논리 경로 |
|---|---|
mod garden { … }선언의 중괄호 안 · 별도 파일 없음 |
crate::garden |
mod garden;
|
crate::garden |
mod vegetables;
|
crate::garden::vegetables |
mod garden { /* ... */ }- 본문은 선언의 중괄호 안에 있습니다.
- 논리 경로:
crate::garden mod garden;src/garden.rs또는src/garden/mod.rs중 하나를 사용합니다.- 논리 경로:
crate::garden mod vegetables;src/garden/vegetables.rs또는src/garden/vegetables/mod.rs중 하나를 사용합니다.- 논리 경로:
crate::garden::vegetables
#[path = "..."]는 외부 모듈 파일 위치를 바꿀 수 있습니다. 따라서 기본 파일 배치는 모듈 트리를 찾는 관례이지, 파일 시스템과 모듈 트리가 항상 같은 구조라는 뜻은 아닙니다.
다음은 모듈과 경로, use, pub이 컴파일러에서 동작하는 방식과, 대부분의 개발자가 코드를 정리하는 방식을 간추린 것입니다. 지금 새 패키지의 기본 에디션은 2024이며, 이 장의 crate:: 경로는 Rust 1.30부터 모든 에디션에서 쓸 수 있습니다.
| 규칙 | 내용 |
|---|---|
| 크레이트 루트에서 시작 | 컴파일러는 크레이트 루트 파일에서 컴파일을 시작하며, 보통 라이브러리는 src/lib.rs, 바이너리는 src/main.rs |
| 모듈 선언 | 크레이트 루트에 mod garden;을 쓰면 컴파일러가 모듈의 본문을 src/garden.rs나 src/garden/mod.rs에서 찾고, mod garden { ... }이면 그 자리의 중괄호 안이 본문 |
| 하위 모듈 선언 | 크레이트 루트가 아닌 파일에서도 모듈을 선언할 수 있으며, src/garden.rs의 mod vegetables;는 src/garden/vegetables.rs나 src/garden/vegetables/mod.rs에서 본문을 찾음 |
| 모듈 안 코드의 경로 | 모듈이 크레이트에 들어오면 공개 규칙이 허용하는 한 크레이트 어디서든 경로로 쓸 수 있으며, garden의 vegetables에 있는 Asparagus는 crate::garden::vegetables::Asparagus |
| 비공개와 공개 | 모듈 안의 코드는 기본으로 부모 모듈에게 비공개이며, 공개하려면 pub mod로 모듈을, 그 안의 아이템마다 pub을 붙여야 함 |
use 키워드 | 스코프 안에서 긴 경로의 줄임 이름을 만들며, use crate::garden::vegetables::Asparagus; 뒤로는 Asparagus만 써도 됨 |
src/garden.rs 옆에 src/garden/vegetables.rs를 두는 방식이 지금 권하는 파일 배치이고, src/garden/mod.rs 방식도 여전히 쓸 수 있지만 같은 모듈에 두 방식을 함께 쓸 수는 없습니다. 파일 배치는 모듈의 본문을 찾는 규칙일 뿐 모듈 트리 자체는 아닙니다. 파일을 만들기만 해서는 모듈이 되지 않고, 부모의 mod 선언이 있어야 트리에 들어옵니다.
이 규칙을 보여 주는 backyard라는 바이너리 크레이트를 만들어 봅니다. cargo new --edition 2024 backyard로 패키지를 만들고, 다음 파일들을 준비합니다.
크레이트 루트는 src/main.rs입니다.
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {plant:?}!");
}pub mod garden;은 컴파일러에게 src/garden.rs에서 garden 모듈의 본문을 가져오라고 알립니다. pub 덕분에 모듈의 이름도 공개되지만, 이 pub이 src/garden.rs 안의 아이템까지 공개하지는 않습니다.
pub mod vegetables;pub mod vegetables;는 src/garden/vegetables.rs에서 vegetables 모듈의 본문을 가져옵니다. garden 모듈이 src/garden.rs에 있으므로, 그 하위 모듈의 파일은 부모 모듈의 이름과 같은 src/garden 폴더 안에서 찾습니다.
#[derive(Debug)]
pub struct Asparagus {}I'm growing Asparagus!cargo run을 실행하면 컴파일러는 main.rs의 mod garden;을 따라 garden.rs를 읽고, 그 안의 mod vegetables;를 따라 garden/vegetables.rs를 읽습니다. use는 그렇게 만들어진 트리의 긴 경로에 Asparagus라는 줄임 이름을 붙일 뿐입니다. 이제 이 규칙들을 하나씩 실제로 해 봅니다.
모듈로 관련된 코드 묶기
모듈은 크레이트의 코드를 읽기 쉽고 다시 쓰기 쉽게 정리하게 해 줍니다. 또 모듈 안의 코드는 기본으로 비공개이므로, 모듈로 아이템의 공개 여부(privacy)도 정할 수 있습니다. 비공개 아이템은 바깥에서 쓰지 못하는 내부 구현이며, 모듈과 그 안의 아이템을 골라 공개하면 바깥의 코드가 그것을 쓰고 의존할 수 있습니다.
예를 들어 식당의 기능을 제공하는 라이브러리 크레이트를 만든다고 합시다. 코드의 구조에 집중하기 위해 식당을 실제로 구현하지는 않고, 본문이 빈 함수의 시그니처만 정의합니다. 식당은 손님을 맞는 홀(front of house)과 손님이 보지 않는 주방(back of house)으로 나뉩니다. 홀에서는 직원이 손님을 자리로 안내하고 주문을 받아 음식을 내고 계산을 하며, 주방에서는 요리사가 요리하고 관리자가 사무를 봅니다. 이 구조대로 함수를 중첩된 모듈에 넣어 크레이트를 정리해 봅니다.
cargo new --lib restaurant으로 restaurant이라는 라이브러리를 만들고, src/lib.rs에 홀을 나타내는 모듈과 함수 시그니처를 적습니다.
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}모듈은 mod 키워드와 모듈 이름으로 정의하고, 본문은 중괄호로 감쌉니다. hosting과 serving처럼 모듈 안에 다른 모듈을 둘 수 있으며, 모듈에는 구조체, 열거형, 상수, 트레이트, 그리고 이 예처럼 함수 같은 다른 아이템도 정의할 수 있습니다.
모듈로 관련된 정의를 묶고 그 관계에 이름을 붙이면, 이 코드를 쓰는 사람은 필요한 정의를 찾으려고 모든 정의를 훑지 않고 묶음을 따라 찾아갈 수 있습니다. 새 기능을 더하는 사람도 코드를 어디에 넣어야 구조가 유지되는지 알 수 있습니다.
앞 글에서 src/main.rs와 src/lib.rs를 크레이트 루트라고 했습니다. 그렇게 부르는 까닭은, 두 파일 가운데 하나의 내용이 크레이트의 모듈 구조, 곧 모듈 트리(module tree)의 맨 위에 있는 crate라는 모듈이 되기 때문입니다. 앞의 코드를 모듈 트리로 나타내면 다음과 같습니다.
이 트리는 모듈이 어떻게 중첩되어 있는지 보여 줍니다. hosting은 front_of_house 안에 있고, hosting과 serving은 같은 모듈 안에 정의된 형제(sibling)입니다. 모듈 A가 모듈 B 안에 있으면 A는 B의 자식(child), B는 A의 부모(parent)라고 합니다. 트리 전체의 맨 위에는 crate라는 모듈이 보이지 않게 있다는 점을 기억해 둡니다.
모듈 트리는 컴퓨터의 폴더 구조와 비슷해 보이며, 실제로 알맞은 비유입니다. 폴더로 파일을 정리하듯이 모듈로 코드를 정리하고, 파일을 찾을 때 경로를 쓰듯이 모듈의 코드를 찾을 때도 경로를 씁니다. 다만 둘이 꼭 같지는 않아서, 파일 하나에 모듈을 여럿 둘 수도 있습니다. src/main.rs와 src/lib.rs가 함께 있다면 두 파일이 한 트리를 나누어 쓰는 것이 아니라, 서로 다른 두 크레이트가 각자 트리를 가집니다.
아래 표는 아이템을 쓰는 곳이 같은 크레이트 안인지 바깥 크레이트인지에 따라 공개 여부를 판단하는 방법과, use와 pub use가 하는 일을 정리한 것입니다.
PRIVACY · NAME BINDING
호출 위치와 공개 경로를 나눠야 접근 가능성을 정확히 판단한다
private 아이템은 정의한 모듈과 그 자손이 사용할 수 있습니다. 외부 크레이트에서 원래 경로로 접근하려면 조상 모듈과 최종 아이템이 모두 공개되어야 하며, 공개 재공개 경로는 예외가 됩니다.
같은 크레이트 안
private 아이템은 그 아이템을 정의한 모듈과 자손 모듈에서 접근할 수 있습니다. 부모나 다른 형제 모듈에서는 해당 private 자식 아이템을 직접 사용할 수 없습니다.
외부 크레이트에서
원래 경로를 쓰려면 경로의 모든 조상 모듈과 최종 아이템이 공개되어야 합니다. pub use가 만든 공개 경로를 사용하면 private 내부 모듈을 API에 노출하지 않을 수 있습니다.
| 선언 | 무엇을 허용하는가 | 놓치기 쉬운 경계 |
|---|---|---|
| 기본 private | 정의 모듈과 그 자손에서 접근 | 부모·형제에게 자동 공개되지 않음 |
pub mod api |
호출 위치에서 조상 경로가 허용하는 범위까지 모듈 이름 공개 | 모듈 안의 함수·타입·필드는 각자 visibility를 유지 |
pub fn run |
접근 가능한 조상 경로 또는 재공개 경로를 통해 함수 공개 | 최종 아이템의 pub만으로 private 조상 경로를 통과하지 못함 |
pub(crate) |
최대 현재 크레이트 범위까지 함수 공개 | 호출 위치에서 함수까지 이어지는 조상 경로도 접근 가능해야 함 |
use 바인딩 |
privacy를 확인한 뒤 현재 스코프에 이름 바인딩 생성 | 모듈을 선언하거나 소스 파일을 불러오는 기능이 아님 |
pub use |
바인딩을 공개해 새 공개 API 경로 생성 | 내부 모듈의 원래 경로와 외부 사용 경로가 달라질 수 있음 |
- 기본 private
- 정의 모듈과 그 자손에서 접근합니다.
- 부모·형제에게 자동 공개되지 않습니다.
pub mod api- 모듈 이름의 visibility만 넓힙니다.
- 내부 함수·타입·필드는 각자 visibility를 유지합니다.
pub fn run- 접근 가능한 조상 경로나 재공개 경로를 통해 함수를 공개합니다.
- 최종 함수만
pub로 두어 private 조상을 통과할 수는 없습니다. pub(crate)- 최대 현재 크레이트 범위까지 함수를 공개합니다.
- 호출 위치에서 조상 경로도 접근 가능해야 합니다.
use바인딩- privacy를 확인하고 현재 스코프에 이름을 바인딩합니다.
- 모듈 선언이나 파일 로딩은 하지 않습니다.
pub use재공개- 바인딩을 공개해 새 공개 API 경로를 만듭니다.
- private 내부 구조와 외부 사용 경로를 분리할 수 있습니다.
오류는 경로 기준점과 철자 → 부모의 mod 선언과 소스 위치 → 호출 위치에서 조상·최종 아이템의 visibility → use 바인딩 또는 pub use 재공개 순서로 확인합니다.
같은 크레이트 안에서는, 비공개 아이템을 그것을 정의한 모듈과 그 자손 모듈이 쓸 수 있지만, 부모나 형제 모듈은 그 안의 비공개 아이템을 쓸 수 없습니다. 바깥 크레이트에서 원래 경로로 아이템을 쓰려면 경로의 모든 조상 모듈과 마지막 아이템이 모두 공개되어 있어야 합니다. use는 이름을 지금의 스코프에 묶기만 하며, pub use는 그 이름까지 공개해 원래 구조와 다른 공개 경로를 만듭니다. pub과 경로는 다음 글에서, use와 pub use는 그다음 글에서 자세히 다룹니다.
정리
- 크레이트는 크레이트 루트에서 시작하며, 그 루트가 모듈 트리의 맨 위인
crate모듈이 됩니다. mod 이름;은 본문을이름.rs나이름/mod.rs에서 가져오고,mod 이름 { ... }은 그 자리에 본문을 둡니다.- 파일만 만들어서는 모듈이 되지 않고, 부모의
mod선언이 있어야 모듈 트리에 들어옵니다. - 모듈 안의 아이템은 기본으로 비공개이며, 정의한 모듈과 그 자손만 쓸 수 있습니다.
pub mod는 모듈의 이름만 공개하며, 그 안의 아이템은 따로pub을 붙여야 공개됩니다.use는 경로의 줄임 이름을 만들 뿐 모듈을 크레이트에 더하지 않습니다.
연습 문제
1. 어디가 막혔을까
backyard의 세 파일에서 다음 변경을 하나씩 따로 적용합니다. 각각 컴파일되나요? 되지 않는다면 무엇 때문인가요? (가) main.rs의 pub mod garden;에서 pub만 지운다. (나) garden.rs의 pub mod vegetables;에서 pub만 지운다. (다) pub struct Asparagus {}에서 pub만 지운다. (라) use 줄을 지우고 crate::garden::vegetables::Asparagus {}로 만든다. (마) main.rs에서 pub mod garden; 줄을 지운다.
해설 보기
(가)는 컴파일됩니다. garden은 크레이트 루트에 정의되었고 main도 크레이트 루트에 있으므로, 같은 모듈 안에서는 비공개인 garden을 쓸 수 있습니다. (나)는 컴파일되지 않습니다. vegetables가 garden 안의 비공개 모듈이 되어, 부모 쪽인 크레이트 루트의 main에서는 그 안으로 들어갈 수 없으므로 모듈이 비공개라는 E0603 오류가 납니다. (다)도 같은 까닭으로 구조체가 비공개라는 E0603 오류가 납니다. (라)는 컴파일됩니다. use는 줄임 이름일 뿐이므로 전체 경로를 쓰면 같은 아이템을 가리킵니다. (마)는 컴파일되지 않습니다. garden.rs 파일이 있어도 mod 선언이 없으면 모듈 트리에 들어오지 않으므로, use crate::garden...이 크레이트 루트에서 garden을 찾지 못합니다.
2. 모듈 트리 그리기
다음 코드의 모듈 트리를 그리고, rotate의 경로를 적어 보세요. 트리의 맨 위에는 크레이트 루트가 있고, 그 아래로 src/lib.rs에 적힌 mod의 중첩 관계를 그대로 따라가면 됩니다. 그 경로로 어디에서 rotate를 부를 수 있는지도 함께 생각해 보세요.
mod kitchen {
mod oven {
fn preheat() {}
}
mod tools {
fn rotate() {}
}
}해설 보기
맨 위의 crate 아래에 kitchen이 있고, kitchen 아래에 형제인 oven과 tools가 있으며, oven에는 preheat가, tools에는 rotate가 있습니다. rotate의 경로는 crate::kitchen::tools::rotate입니다. 다만 모듈과 함수가 모두 비공개이므로, 이 경로로 rotate를 부를 수 있는 곳은 tools 모듈과 그 자손뿐입니다.
다음 글 모듈 경로와 아이템 참조에서는 절대 경로와 상대 경로로 모듈 트리의 아이템을 가리키는 방법과, pub으로 아이템을 공개하는 방법을 살펴봅니다.