모듈로 스코프와 비공개 여부 제어하기

모듈로 스코프와 비공개 여부 제어하기 (Control Scope and Privacy with Modules)

프로그램이 커지면서 "어떤 코드가 어떤 이름에 접근할 수 있는지"를 통제하는 일이 중요해져요. 그 역할을 하는 게 모듈 시스템의 핵심 요소들이에요. 항목에 이름을 붙이는 경로(paths), 경로를 스코프로 가져오는 use 키워드, 항목을 공개로 만드는 pub 키워드가 그것이죠. 이번 절에서는 이들을 비롯해 as 키워드, 외부 패키지, 글롭 연산자까지 하나씩 살펴볼게요.

출처: The Rust Book — 모듈로 스코프와 비공개 여부 제어하기

모듈 치트 시트 (Modules Cheat Sheet)

모듈과 경로의 세부 사항으로 들어가기 전에, 먼저 컴파일러에서 모듈, 경로, use 키워드, pub 키워드가 어떻게 동작하는지, 그리고 대부분의 개발자가 코드를 어떻게 조직하는지에 대한 빠른 참조를 정리해 드릴게요. 이 장 전체에서 각 규칙에 대한 예시를 차례로 다루겠지만, 모듈이 어떻게 동작하는지 되새기고 싶을 때 여기를 참고하면 좋아요.

  • 크레이트 루트에서 시작하기: 컴파일러는 크레이트를 컴파일할 때 먼저 크레이트 루트 파일(보통 라이브러리 크레이트는 src/lib.rs, 바이너리 크레이트는 src/main.rs)에서 컴파일할 코드를 찾아요.

  • 모듈 선언하기: 크레이트 루트 파일에서 새 모듈을 선언할 수 있어요. mod garden;라고 "garden" 모듈을 선언했다고 해 볼게요. 컴파일러는 그 모듈의 코드를 다음 위치들에서 찾아요.

    • mod garden 다음의 세미콜론을 대체하는 중괄호 안(인라인)
    • src/garden.rs 파일
    • src/garden/mod.rs 파일
  • 하위 모듈 선언하기: 크레이트 루트가 아닌 어떤 파일에서든 하위 모듈(submodule)을 선언할 수 있어요. 예를 들어 src/garden.rs에서 mod vegetables;를 선언했다고 해 볼게요. 컴파일러는 하위 모듈의 코드를 부모 모듈 이름을 딴 디렉터리 안에서 다음 위치들로 찾아요.

    • mod vegetables 바로 뒤, 세미콜론 대신 중괄호 안(인라인)
    • src/garden/vegetables.rs 파일
    • src/garden/vegetables/mod.rs 파일
  • 모듈 안 코드로 가는 경로: 모듈이 일단 크레이트의 일부가 되면, 비공개 규칙(privacy rules)이 허용하는 한 크레이트 안 어디에서든 그 모듈 안의 코드를 경로를 써서 참조할 수 있어요. 예를 들어 garden vegetables 모듈에 있는 Asparagus 타입은 crate::garden::vegetables::Asparagus에서 찾을 수 있어요.

  • 비공개 vs 공개: 모듈 안의 코드는 기본적으로 부모 모듈로부터 비공개(private)예요. 모듈을 공개로 만들려면 mod 대신 pub mod로 선언해요. 공개 모듈 안의 항목도 공개로 만들려면 그 선언 앞에 pub를 붙이면 돼요.

  • use 키워드: 스코프 안에서 use 키워드는 길고 반복되는 경로를 줄여 주는 단축키(shortcut)를 만들어 줘요. crate::garden::vegetables::Asparagus를 참조할 수 있는 어떤 스코프에서든 use crate::garden::vegetables::Asparagus;로 단축키를 만들 수 있고, 그 후로는 그 스코프 안에서 Asparagus만 써서 그 타입을 사용하면 돼요.

이 규칙들을 설명하기 위해 backyard라는 이름의 바이너리 크레이트를 만들어 볼게요. 크레이트의 디렉터리(역시 backyard라는 이름)에는 이런 파일과 디렉터리가 들어 있어요.

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs

이 경우 크레이트 루트 파일은 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에서 찾은 코드를 포함하라고 말해요. 그 코드는 다음과 같아요.

pub mod vegetables;

여기서 pub mod vegetables;src/garden/vegetables.rs의 코드도 포함된다는 뜻이에요. 그 코드는 다음과 같아요.

#[derive(Debug)]
pub struct Asparagus {}

이제 이 규칙들의 세부 사항으로 들어가서 실제로 어떻게 동작하는지 확인해 볼게요.

모듈은 크레이트 안의 코드를 가독성과 재사용을 위해 조직하게 해 줍니다. 또한 모듈 안의 코드는 기본적으로 비공개이기 때문에 항목의 비공개 여부를 제어하게 해 줘요. 비공개 항목은 내부 구현 세부 사항으로, 외부에서 사용할 수 없어요. 모듈과 그 안의 항목을 공개로 만들면 외부 코드가 사용하고 의존할 수 있게 노출되는 거예요.

예를 들어 레스토랑의 기능을 제공하는 라이브러리 크레이트를 하나 만들어 볼게요. 함수의 시그니처(signature)만 정의하고 본문은 비워 둘게요. 레스토랑의 구현보다는 코드의 조직에 집중하기 위해서예요.

레스토랑 업계에서는 어떤 부분을 프론트 오브 하우스(front of house)라고 하고, 다른 부분을 백 오브 하우스(back of house)라고 불러요. 프론트 오브 하우스는 손님이 있는 곳이에요. 호스트가 손님을 좌석으로 안내하고, 서버가 주문과 결제를 받고, 바텐더가 음료를 만드는 곳이죠. 백 오브 하우스는 주방에서 요리사와 조리사가 일하고, 설거지 담당이 청소하고, 매니저가 사무 업무를 보는 곳이에요.

크레이트를 이런 식으로 구조화하기 위해, 함수들을 중첩된 모듈로 조직할 수 있어요. cargo new restaurant --librestaurant이라는 새 라이브러리를 만든 뒤, Listing 7-1의 코드를 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 키워드 다음에 모듈의 이름(여기서는 front_of_house)을 붙여 모듈을 정의해요. 모듈의 본문은 중괄호 안에 들어가요. 모듈 안에는 다른 모듈을 둘 수도 있는데, 여기서 hostingserving 모듈이 그렇죠. 모듈은 struct, enum, 상수, 트레이트, 그리고 Listing 7-1의 함수 같은 다른 항목의 정의도 담을 수 있어요.

모듈을 사용하면 관련된 정의끼리 묶고, 왜 관련이 있는지 이름으로 설명할 수 있어요. 이 코드를 사용하는 프로그래머는 모든 정의를 처음부터 끝까지 읽는 대신 그룹을 따라 코드를 탐색할 수 있어서, 자기에게 관련된 정의를 더 쉽게 찾을 수 있어요. 이 코드에 새 기능을 추가하는 프로그래머는 프로그램을 정리된 상태로 유지하려면 코드를 어디에 넣어야 할지 알게 되죠.

앞서 src/main.rssrc/lib.rs크레이트 루트(crate roots) 라고 부른다고 했어요. 그 이름이 붙은 이유는 이 두 파일 중 하나의 내용이 크레이트의 모듈 구조 루트에 있는 crate라는 이름의 모듈을 이루기 때문이에요. 이 구조를 모듈 트리(module tree) 라고 해요.

Listing 7-2는 Listing 7-1 구조의 모듈 트리를 보여 줘요.

crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

이 트리는 어떤 모듈들이 다른 모듈 안에 중첩되는지 보여 줘요. 예를 들어 hostingfront_of_house 안에 중첩돼 있죠. 트리는 또 어떤 모듈들이 형제(siblings) 인지도 보여 줘요. 형제란 같은 모듈 안에 정의된 것들을 말해요. hostingservingfront_of_house 안에 정의된 형제예요. 모듈 A가 모듈 B 안에 들어 있으면, 모듈 A는 모듈 B의 자식(child)이고, 모듈 B는 모듈 A의 부모(parent)라고 해요. 전체 모듈 트리가 crate이라는 암묵적 모듈 아래 뿌리를 내리고 있다는 점도 눈여겨보세요.

모듈 트리를 보면 컴퓨터의 파일시스템 디렉터리 트리가 떠오를지도 몰라요. 그 비교가 아주 적절해요! 파일시스템의 디렉터리처럼 모듈로 코드를 조직하는 거예요. 그리고 디렉터리의 파일처럼, 우리 모듈을 찾을 방법도 필요하죠. 그 방법이 바로 다음 절에서 다룰 경로(paths)예요.

더 알아보기 (Learn more)