4단계 위계
Package
└─ Crate (1개 이상)
└─ Module (중첩 가능)
└─ Item (function, struct, enum, ...)
| 단계 | 의미 | 비유 |
|---|---|---|
| Package | Cargo가 다루는 단위. Cargo.toml 하나당 하나 | Maven/Gradle 프로젝트 |
| Crate | 컴파일러가 한 번에 처리하는 단위 | jar 하나, .so/.dll 하나 |
| Module | crate 내부의 namespace + privacy 경계 | Java package |
| Item | 실제 코드 조각 | function, class |
Crate — binary와 library
- Binary crate —
main함수 보유, 실행 파일로 컴파일됨 - Library crate —
main없음, 다른 crate가 의존성으로 가져다 씀
한 package는 binary crate 여러 개 + library crate 최대 하나를 가질 수 있다. Cargo 관습:
| 파일 | 역할 |
|---|---|
src/main.rs | binary crate의 root, 패키지 이름과 동일 |
src/lib.rs | library crate의 root |
src/bin/*.rs | 추가 binary crate들 |
가장 흔한 패턴: library crate(lib.rs)가 진짜 코드를 다 들고, binary crate(main.rs)는 그 라이브러리를 호출하는 얇은 진입점. cargo 자체도 이 패턴이며 이렇게 하면 라이브러리를 다른 사람이 의존할 수 있다.
Module — namespace + privacy
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
mod 키워드로 정의. module은 두 역할을 동시에 한다.
namespace — 같은 이름이라도 다른 module 안에 있으면 충돌하지 않는다.
privacy 경계 — 기본 private. 부모는 자식 내부를 못 보고, 자식은 부모/조상을 볼 수 있다. OOP의 private/public과 비슷하지만 단위가 module이지 class가 아니다. 같은 module 안 struct들은 서로의 private 필드를 본다 — Rust의 "friend"는 module 단위로 자동 작동한다.
Module Tree
crate (lib.rs / main.rs) ← 암시적 루트
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
└── serve_order
파일 시스템의 디렉토리 트리와 정확히 같은 비유. Rust 컴파일러가 path를 풀어내는 기준 구조다.
Path — module tree에서 item 가리키기
두 가지 형태.
// absolute (crate root부터)
crate::front_of_house::hosting::add_to_waitlist();
// relative (현재 module부터)
front_of_house::hosting::add_to_waitlist();
특수 키워드.
super::foo— 부모 module의 foo (filesystem의..)self::foo— 현재 module의 foo
book 권장은 absolute path — 코드 이동이 잦으니 root부터 명시하는 게 안정적.
pub 키워드 — 공개 제어
기본 private이라 부모가 자식 내부에 접근하려면 pub 필요.
mod front_of_house {
pub mod hosting { // module 자체 공개
pub fn add_to_waitlist() {} // 안의 item 공개
}
}
module을 pub으로 한다고 그 안의 모든 게 public이 되진 않는다. 각 item에 따로 pub을 붙여야 한다.
struct vs enum의 pub 차이
mod back_of_house {
pub struct Breakfast {
pub toast: String, // 공개
seasonal_fruit: String, // 비공개 (기본)
}
pub enum Appetizer {
Soup, // 자동 공개
Salad, // 자동 공개
}
}
- struct: 타입 자체와 각 필드의 공개 여부가 분리. 부분 공개가 자연스럽다.
- enum: variant가 다 공개되거나 다 비공개거나. variant를 분기 처리하려면 다 보여야 의미가 있어서 default가 자동 공개.
use — path 단축
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
filesystem의 symbolic link와 같은 비유. 그 scope 안에서만 유효하며, 자식 module로 들어가면 다시 가져와야 한다.
관용 패턴 — function vs struct/enum
// 함수: 부모 module까지만 use → 호출 때 출처 힌트
use crate::front_of_house::hosting;
hosting::add_to_waitlist();
// struct/enum: 끝까지 use
use std::collections::HashMap;
let mut map = HashMap::new();
함수는 호출 시 출처가 안 보이면 헷갈리고, 타입은 매번 적게 되니 끝까지 가져와도 괜찮다는 컨벤션.
as — 별칭
이름 충돌 시.
use std::fmt::Result;
use std::io::Result as IoResult;
pub use — 재노출
내부 구조와 외부 노출 구조를 분리할 때.
pub use crate::front_of_house::hosting;
// 외부에서 restaurant::hosting::add_to_waitlist()로 호출 가능
라이브러리 작성 시 "내 코드 구조"와 "사용자가 보는 API"를 분리하는 강력한 도구.
외부 패키지
# Cargo.toml
[dependencies]
rand = "0.8.5"
use rand::Rng;
std는 자동으로 들어와서 Cargo.toml에 적지 않아도 되지만 use는 필요하다.
중첩 path와 glob
use std::{cmp::Ordering, io};
use std::io::{self, Write};
use std::collections::*; // glob — 가독성 해치니 조심
파일 분리 — mod는 include도 import도 아니다
이게 다른 언어와 결정적으로 다른 지점.
src/
├── lib.rs
├── front_of_house.rs ← front_of_house module 본체
└── front_of_house/
└── hosting.rs ← hosting module 본체
// src/lib.rs
mod front_of_house; // 컴파일러야, front_of_house가 어딘가 있어!
mod foo;는 선언이다. 컴파일러가 다음에서 본체를 찾는다.
src/foo.rs(권장)src/foo/mod.rs(옛 스타일)
자식 module인 hosting은 front_of_house 디렉토리 안에 들어가야 한다 — module tree 구조와 디렉토리 구조가 일치.
다른 언어와의 매핑
| 언어 | 가져오기 메커니즘 |
|---|---|
| Rust | mod (선언) + use (단축) — 두 단계 분리 |
| Java | 디렉토리에서 자동 추론 + import |
| Python | import (모듈 로딩 + namespace 가져옴) |
| C++ | #include (텍스트 복붙, namespace는 별개) |
Rust에서 한 module은 module tree에 정확히 한 번만 mod로 선언돼야 한다. 같은 파일을 여러 번 mod 하면 같은 코드가 중복 컴파일되어 충돌. mod는 "코드 포함" 명령이지 "import"가 아님을 기억할 것.
작은 프로젝트 흐름
calculator/
├── Cargo.toml
└── src/
├── lib.rs
└── ops/
├── arithmetic.rs
└── trig.rs
// src/lib.rs
pub mod ops;
// src/ops.rs ← ops module 본체 (서브모듈 선언 위치)
pub mod arithmetic;
pub mod trig;
// src/ops/arithmetic.rs
pub fn add(a: f64, b: f64) -> f64 { a + b }
// src/ops/trig.rs
pub fn sin(x: f64) -> f64 { x.sin() }
외부 사용자.
use calculator::ops::arithmetic;
use calculator::ops::trig::sin;
Module tree와 디렉토리 구조의 일치가 한눈에 보인다.
결론
Package(Cargo.toml) > Crate(main/lib.rs) > Module(mod 선언, 디렉토리와 일치) > Item의 4단계 위계가 module system의 전부다. mod는 선언(C++ #include나 Java import와 다름), use는 path 단축. pub으로 공개 제어, pub use로 내부 구조와 외부 API 분리. 디렉토리 구조와 module tree가 1:1 대응되도록 파일을 배치하는 게 관용.