콘텐츠로 이동

Rust 모듈

지금까지의 예제는 모두 main.rs 파일 하나에 들어 있었어요. 작은 프로그램이라면 그걸로 충분하지만, 실제 프로젝트에서는 코드를 의미 있는 덩어리로 나눠요. 이때 쓰는 Rust의 도구가 바로 모듈 시스템(module system), 즉 mod, pub, use예요.

모듈은 서로 관련된 코드를 하나로 묶어 주는, 이름이 붙은 블록이에요.

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

실행 결과:

5
▶ 직접 실행해 보기

여기서 두 가지를 눈여겨보세요. 함수를 부를 때 math::add(...)처럼 모듈 이름을 경로로 붙여서 불러요. 그리고 함수에 pub이 붙어 있어요. pub이 없으면 모듈 바깥에서는 이 함수에 접근할 수 없어요.

처음 보면 꽤 놀라는 부분이라 따로 짚고 넘어갈게요. 모듈 안의 내용은 기본적으로 비공개(private)예요. 위 예제에서 pub을 지우면 컴파일되지 않아요.

mod math {
fn add(a: i32, b: i32) -> i32 { // `pub`이 없음
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ 에러: function `add` is private
println!("{sum}");
}

모듈 바깥에서 쓰게 하고 싶은 것에는 하나하나 직접 pub을 붙여야 해요. 일부러 이렇게 만든 거예요. 덕분에 모듈은 내부에서만 쓰는 도우미 함수는 숨기고, 공개 API로 내놓을 부분만 보여 줄 수 있어요.

모듈 안에 다른 모듈을 넣을 수도 있어요. 큰 프로젝트는 이런 식으로 나무 같은 구조를 만들어요.

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ 직접 실행해 보기

매번 shapes::circle::area(...)라고 쓰다 보면 금방 지쳐요. use는 경로를 스코프 안으로 가져와서 짧게 부를 수 있게 해 줘요.

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // shapes::circle::area보다 짧아요
println!("{area}");
}
▶ 직접 실행해 보기

사실 use는 이미 써 봤어요. HashMap 페이지에서 본 use std::collections::HashMap;이 바로 같은 키워드예요.

위의 예제들은 모두 파일 하나에 들어 있어요. Rust Playground에서는 그렇게 해야 하거든요. 하지만 내 컴퓨터에서 실제 프로젝트를 만들 때는 보통 모듈마다 파일을 따로 둬요. math 모듈이 있는 프로젝트라면 대개 이런 모양이에요.

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // Rust에게 "이 모듈의 코드는 math.rs에서 찾아"라고 알려 줘요
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

본문 없이 세미콜론으로 끝나는 mod math;는 같은 폴더의 math.rs에서 모듈 내용을 불러오라는 뜻이에요. 프로젝트가 파일 하나를 넘어서기 시작하면 바로 이 방식을 쓰게 될 거예요.

convert라는 모듈을 만들고, 그 안에 공개 함수 두 개를 넣어 보세요. c_to_f(섭씨를 화씨로)와 f_to_c(그 반대)예요. main에서 100°C는 212°F와 212°F는 100°C를 출력하세요.

힌트: 공식은 c * 9.0 / 5.0 + 32.0과 (f - 32.0) * 5.0 / 9.0이에요. pub을 잊지 말고, convert::c_to_f(100.0)처럼 모듈 이름을 붙여서 함수를 부르세요.

정답 보기
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°C는 {}°F", convert::c_to_f(100.0));
println!("212°F는 {}°C", convert::f_to_c(212.0));
}

실행 결과:

100°C는 212°F
212°F는 100°C
▶ 직접 실행해 보기

두 함수 모두 pub이라서, 모듈 바깥에 있는 main에서 부를 수 있어요. pub 하나를 지워 보면 컴파일러가 그 함수는 비공개라고 알려 줄 거예요.

중첩 모듈 school::grades를 만들고, 그 안에 공개 함수 letter(score: u32) -> char를 넣으세요. 90점 이상이면 'A', 80점 이상이면 'B', 그 밖에는 'C'를 돌려줘요. grades 안에는 letter가 사용하는 비공개 도우미 함수 is_at_least(score: u32, min: u32) -> bool도 만드세요. use로 school::grades를 스코프로 가져온 다음, 95, 85, 70점의 등급을 출력하세요.

힌트: school 안의 mod grades와 fn letter에는 둘 다 pub이 필요하지만, is_at_least에는 붙이면 안 돼요. 같은 모듈 안의 코드는 비공개 함수를 언제든 부를 수 있어요.

정답 보기
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// 비공개: `grades` 안의 코드만 부를 수 있어요
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

실행 결과:

95 -> A
85 -> B
70 -> C
▶ 직접 실행해 보기

letter와 is_at_least는 같은 모듈에 있으니 letter에서 is_at_least를 부를 수 있어요. 하지만 main에서 grades::is_at_least(95, 90)을 부르면 “function is_at_least is private” 에러가 나요. 바로 이게 핵심이에요. 모듈은 작은 공개 API만 보여 주고, 도우미 함수는 안에 감춰 둬요.

  • mod 이름 { ... }은 관련된 코드를 이름 붙은 모듈로 묶어요.
  • 모듈 안의 모든 것은 기본적으로 비공개예요. 모듈 바깥에 보여 주려면 pub을 붙이세요.
  • use는 경로를 스코프로 가져와서, 매번 전체 경로를 쓰지 않아도 되게 해 줘요.
  • 여러 파일로 된 프로젝트에서는 본문 없는 mod 이름;이 같은 이름의 파일에서 모듈 코드를 불러와요.
  • 모듈 안의 코드는 그 모듈의 비공개 항목을 언제든 쓸 수 있어요. 그러니 도우미 함수는 비공개로 두고, 바깥에서 꼭 필요한 것만 공개하세요.

이해도 점검

1. mod 안의 항목은 기본적으로 공개일까요, 비공개일까요?

2. 모듈 코드를 직접 쓰지 않고 math.rs에서 불러오게 하려면 어떻게 써야 할까요?

점수: 0 / 2