Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

모듈(Module)

방금 정의한 new 메서드는 Ticket의 필드 값들에 몇 가지 제약 조건을 걸려고 합니다. 하지만 이 규칙(Invariants)이 정말로 엄격하게 지켜질까요? 다른 개발자가 Ticket::new를 통하지 않고 마음대로 Ticket 인스턴스를 만드는 걸 어떻게 막을 수 있을까요?

데이터를 안전하게 보호하는 **캡슐화(Encapsulation)**를 제대로 구현하려면, **가시성(Visibility)**과 **모듈(Module)**이라는 두 가지 핵심 개념을 꼭 알아야 합니다. 먼저 모듈부터 시작해 봅시다.

모듈(Module)이란 무엇인가요?

Rust에서 모듈은 서로 관련된 코드들을 하나의 이름표(네임스페이스, Namespace) 아래로 묶어 관리하는 방법입니다. 이미 모듈을 사용해 본 적이 있을 거예요. 코드의 동작을 확인하는 단위 테스트들을 tests라는 별도의 모듈에 담아두었으니까요.

#[cfg(test)]
mod tests {
    // [...] 
}

인라인 모듈(Inline Module)

위의 tests 모듈은 인라인 모듈의 예시입니다. 모듈 선언(mod tests)과 모듈의 내용({ ... } 안의 코드)이 한 파일에 나란히 붙어 있죠.

모듈 트리(Module Tree)

모듈은 서로 중첩되어 트리(Tree) 구조를 형성할 수 있습니다. 트리의 뿌리(Root)는 크레이트(Crate) 그 자체이며, 다른 모든 모듈을 품고 있는 최상위 모듈이 됩니다. 라이브러리 크레이트라면 보통 src/lib.rs 파일이 루트 모듈(혹은 크레이트 루트) 역할을 합니다.

크레이트 루트는 하위 모듈을 가질 수 있고, 그 하위 모듈은 또 자신만의 하위 모듈을 가질 수 있습니다.

외부 모듈과 파일 시스템

인라인 모듈은 코드가 짧을 때는 편하지만, 프로젝트가 커지면 코드를 여러 파일로 나누어 관리하는 것이 좋습니다. 이때 부모 모듈에서 mod 키워드를 사용해 하위 모듈이 존재함을 선언합니다.

mod dog;

Rust의 빌드 도구인 cargo는 선언된 모듈의 실제 코드가 어디 있는지 찾아내는 역할을 합니다. 만약 모듈이 크레이트 루트(src/lib.rssrc/main.rs)에 선언되었다면, cargo는 다음 위치에서 파일을 찾습니다:

  • src/<모듈_이름>.rs
  • src/<모듈_이름>/mod.rs

만약 다른 모듈 안에 속한 하위 모듈이라면 경로는 다음과 같아집니다:

  • [..]/<부모_모듈>/<모듈_이름>.rs
  • [..]/<부모_모듈>/<모듈_이름>/mod.rs

예를 들어 doganimals의 하위 모듈이라면, 파일 위치는 src/animals/dog.rs 또는 src/animals/dog/mod.rs가 됩니다. 요즘 IDE들은 mod 키워드로 새 모듈을 선언할 때 이런 파일들을 자동으로 만들어주기도 해서 아주 편리합니다.

아이템 경로(Item Path)와 use

같은 모듈 안에 있는 아이템(구조체, 함수 등)은 복잡한 주소 없이 이름만으로 바로 부를 수 있습니다.

struct Ticket {
    // [...] 
}

// 같은 모듈 안에 있으므로 `Ticket` 앞에 아무것도 붙일 필요가 없습니다.
fn mark_ticket_as_done(ticket: Ticket) {
    // [...] 
}

하지만 다른 모듈에 있는 엔티티에 접근할 때는 그 엔티티가 어디에 있는지 알려주는 **경로(Path)**를 명시해야 합니다. 경로를 지정하는 방법은 몇 가지가 있습니다:

  • 현재 크레이트의 루트부터 시작: 예) crate::module_1::MyStruct
  • 부모 모듈부터 시작: 예) super::my_function
  • 현재 모듈의 하위 모듈부터 시작: 예) sub_module_1::MyStruct

여기서 cratesuper는 예약된 키워드입니다. crate는 현재 크레이트의 뿌리를, super는 현재 모듈의 부모를 가리킵니다.

매번 이렇게 긴 경로를 다 적으려면 참 번거롭겠죠? 이럴 때 use 문을 사용하면 특정 엔티티를 현재 범위(Scope)로 가져와서 이름만으로 편하게 쓸 수 있습니다.

// `MyStruct`를 현재 범위로 가져옵니다.
use crate::module_1::module_2::MyStruct;

// 이제 `MyStruct`를 직접 부를 수 있습니다.
fn a_function(s: MyStruct) {
     // [...] 
}

와일드카드(Wildcard) 가져오기

use 문 하나로 모듈 안의 모든 항목을 한꺼번에 가져올 수도 있습니다.

use crate::module_1::module_2::*;

이를 와일드카드 가져오기(Glob Import) 또는 별표 가져오기라고 부릅니다. 하지만 이 방식은 현재 네임스페이스를 지저분하게 만들고, 이름이 어디서 왔는지 헷갈리게 하며, 이름 충돌을 일으킬 위험이 있어 꼭 필요한 경우가 아니라면 피하는 것이 좋습니다. 다만 테스트 모듈에서 부모 모듈의 모든 내용을 가져오기 위해 use super::*;를 사용하는 것은 흔히 볼 수 있는 패턴입니다.

모듈 트리 시각화하기

내 프로젝트의 모듈 구조가 한눈에 들어오지 않는다면, cargo-modules 같은 도구를 사용해 시각화해 보는 것도 좋은 방법입니다! 설치 및 사용 방법은 해당 도구의 문서를 참고해 보세요.

Exercise

The exercise for this section is located in 03_ticket_v1/03_modules