샘플 프로젝트 생성
Rust 프로젝트를 시작하려면 cargo
를 사용하여 새로운 프로젝트를 생성합니다.
$ cargo new rust_modules
cargo new
명령은 기본적으로 바이너리 프로젝트를 생성합니다. Cargo.toml
파일과 함께 src
디렉터리 내에 main.rs
파일이 생성됩니다.
모듈과 크레이트
모듈에 대해 설명하기 전에 크레이트에 대해 먼저 알아보겠습니다.
크레이트는 Rust 프로그램의 가장 큰 단위입니다. 크게 두 가지로 나뉩니다.
- 라이브러리 크레이트 (
lib.rs
): 재사용 가능한 코드를 포함하는 라이브러리입니다. - 바이너리 크레이트 (
main.rs
): 실행 가능한 프로그램을 만듭니다.
각 크레이트의 루트 파일은 lib.rs
또는 main.rs
이며, 이 파일에서 다른 모든 모듈을 참조합니다. 프로젝트 내에 파일이 존재하더라도 루트 파일(즉, lib.rs
또는 main.rs
)에서 추적하지 않으면 컴파일되지 않습니다.
모듈 생성 방법
모듈을 생성하는 방법은 크게 두 가지가 있습니다.
- 한 파일 내에 인라인으로 생성
- 별도의 파일로 생성
1. 인라인 모듈
인라인 모듈은 한 파일 내에서 모듈을 정의하는 방법입니다. 보통 설명을 위해 사용하거나 접근성을 제어하기 위해 사용됩니다.
lib.rs
파일에서 인라인 모듈을 생성하는 예제는 다음과 같습니다.
// src/lib.rs
mod inline_module {
pub fn greet() {
println!("Hello from inline module!");
}
}
모듈에 정의된 greet
함수는 inline_module::greet
으로 접근할 수 있습니다.
// src/main.rs
fn main() {
rust_modules::inline_module::greet();
}
2. 파일로 모듈 생성
별도의 파일로 모듈을 생성하려면 먼저 파일을 만들어야 합니다.
$ tree src
src
├── lib.rs
└── new_module.rs
그리고 lib.rs
에서 mod
키워드로 참조하면 사용 가능합니다.
// src/lib.rs
mod new_module;
new_module.rs
파일에서 greet
함수를 정의합니다.
// src/new_module.rs
pub fn greet() {
println!("Hello from new_module!");
}
이제 main.rs
에서 이를 호출할 수 있습니다.
// src/main.rs
use rust_modules::new_module;
fn main() {
new_module::greet();
}
디렉터리로 서브 모듈 생성
모듈을 디렉터리 구조로 중첩해서 만들 수 있습니다. 예를 들어 new_module
모듈 안에 서브 모듈을 만들려면 다음과 같이 디렉터리를 생성합니다.
$ tree src
src
├── lib.rs
├── new_module
│ └── new_sub_module.rs
└── new_module.rs
그리고 new_module.rs
에서 서브 모듈을 참조합니다.
// src/new_module.rs
mod new_sub_module;
new_sub_module.rs
파일에서 greet
함수를 정의합니다.
// src/new_module/new_sub_module.rs
pub fn greet() {
println!("Hello from new_sub_module!");
}
이제 main.rs
에서 이를 호출할 수 있습니다.
// src/main.rs
use rust_modules::new_module::new_sub_module;
fn main() {
new_sub_module::greet();
}
2015 버전 모듈 구조의 유산
2015 버전에서는 모듈을 디렉터리로 만들 때 mod.rs
파일을 사용했습니다. 예를 들어 new_module.rs
대신 new_module/mod.rs
를 사용할 수 있습니다.
$ tree src
src
├── lib.rs
└── new_module
└── mod.rs
서브 모듈을 만드는 예는 다음과 같습니다.
$ tree src
src
├── lib.rs
└── new_module
├── mod.rs
└── new_sub_module.rs
mod.rs
에서 서브 모듈을 참조합니다.
// src/new_module/mod.rs
mod new_sub_module;
하지만 2018 에디션 이후로는 디렉터리 내부의 mod.rs
대신 디렉터리 자체를 모듈로 취급하는 방식을 권장합니다.
$ tree src
src
├── lib.rs
└── new_module
└── new_module.rs
형제 모듈 간 접근 제어
형제 모듈 간의 가시성을 살펴보겠습니다. 우선 두 개의 모듈을 만듭니다. lib.rs
에는 module_a
만 선언합니다.
$ tree src
src
├── lib.rs
├── module_a.rs
└── module_b.rs
lib.rs
의 내용은 다음과 같습니다.
// src/lib.rs
mod module_a;
그리고 module_a.rs
에서 module_b
를 서브 모듈로 선언해봅니다.
// src/module_a.rs
mod module_b;
이 상태에서 컴파일하면 다음과 같은 오류가 발생합니다.
$ cargo build
error[E0583]: file not found for module `module_b`
--> src/module_a.rs:1:5
|
1 | mod module_b;
| ^^^^^^^^
|
가장 상위에 있는 lib.rs
만 다른 모듈을 선언할 수 있으므로 module_a
에서는 module_b
를 선언할 수 없습니다.
모듈의 경로 이름
이번에는 lib.rs
에서 module_a
와 module_b
모두를 선언하고 각 모듈에 함수를 추가해봅니다.
$ tree src
src
├── lib.rs
├── module_a.rs
└── module_b.rs
lib.rs
의 내용은 다음과 같습니다.
// src/lib.rs
mod module_a;
mod module_b;
module_a.rs
파일에 함수를 추가합니다.
// src/module_a.rs
pub fn name() {
println!("This is module_a");
}
이때 module_b
에서 module_a
의 함수를 사용할 때 경로 이름은 다음과 같습니다.
// src/module_b.rs
use crate::module_a::name; // 절대 경로
use super::module_a::name; // 상대 경로
서브 모듈과 접근 제어
서브 모듈과 접근 제어를 알아봅시다. 다음과 같이 디렉터리 구조를 만듭니다.
$ tree src
src
├── lib.rs
├── module_a
│ └── submodule.rs
├── module_a.rs
└── module_b.rs
lib.rs
에서는 module_a
와 module_b
모두를 모듈로 선언합니다.
// src/lib.rs
mod module_a;
mod module_b;
module_a.rs
에서는 서브 모듈을 선언합니다.
// src/module_a.rs
mod submodule;
이때 submodule
에서 module_b
를 참조하려면 다음과 같습니다.
// src/module_a/submodule.rs
use crate::module_b; // 절대 경로
use super::super::module_b; // 상대 경로
하지만 module_b
에서 submodule
을 참조하려고 하면 보이지 않습니다. 이는 module_a
가 submodule
을 공개하지 않았기 때문입니다. module_a
가 submodule
을 공개하려면 pub mod
를 사용해야 합니다.
// src/module_a.rs
pub mod submodule;
이렇게 하면 module_b
에서도 다음과 같이 submodule
을 참조할 수 있습니다.
// src/module_b.rs
use crate::module_a::submodule;
use super::module_a::submodule;
모듈의 이름 변경
모듈의 이름을 변경하려면 use
를 사용합니다.
// src/lib.rs
mod module_a;
use self::module_a as a;
이렇게 하면 이후부터 a
라는 모듈이 있는 것처럼 사용할 수 있습니다.
// src/lib.rs
mod module_a;
pub use self::module_a as a;
이렇게 pub use
를 사용하여 공개할 수도 있습니다.
크레이트 외부에서 모듈 사용
이제 main.rs
를 추가하여 크레이트 외부에서 모듈을 사용하는 방법을 알아봅시다.
$ tree src
src
├── lib.rs
├── main.rs
├── module_a
│ └── submodule.rs
├── module_a.rs
└── module_b.rs
lib.rs
의 내용은 다음과 같습니다.
// src/lib.rs
mod module_a;
mod module_b;
module_a.rs
에서는 서브 모듈을 공개합니다.
// src/module_a.rs
pub mod submodule;
main.rs
에서 module_b
를 사용할 수 없습니다. lib.rs
에서 module_b
를 pub
으로 공개하지 않았기 때문입니다.
// src/lib.rs
mod module_a;
pub mod module_b;
이렇게 pub
으로 공개하면 main.rs
에서 다음과 같이 모듈을 사용할 수 있습니다.
// src/main.rs
use rust_modules::module_b;
fn main() {
module_b::name();
}
외부 크레이트 이름 변경
Cargo.toml
에서 외부 크레이트 이름을 변경할 수 있습니다. 예를 들어 rand
크레이트를 random
으로 이름을 변경하여 사용하려면 다음과 같이 설정합니다.
# Cargo.toml
[dependencies]
rand = "0.8"
main.rs
에서 이렇게 사용합니다.
// src/main.rs
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let n: u32 = rng.gen_range(0..10);
println!("Random number: {}", n);
}
extern crate
와 모듈 리네이밍
Rust 2018 에디션 이후로 extern crate
는 필수가 아니지만, 여전히 모듈을 리네이밍할 때 사용할 수 있습니다.
// src/lib.rs
extern crate rand as random;
pub use self::random::Rng;
이렇게 하면 이후에 random
이라는 이름으로 사용할 수 있습니다.
// src/main.rs
use rust_modules::Rng;
fn main() {
let mut rng = rand::thread_rng();
let n: u32 = rng.gen_range(0..10);
println!("Random number: {}", n);
}
모듈을 공개하지 않고 서브 모듈만 공개하기
lib.rs
에서 모듈을 공개하지 않고 서브 모듈만 공개하려면 pub use
를 사용합니다.
// src/lib.rs
mod module_a;
pub mod module_b;
pub use self::module_a::submodule as module_c;
이렇게 하면 main.rs
에서 다음과 같이 사용할 수 있습니다.
// src/main.rs
use rust_modules::module_c;
fn main() {
module_c::greet();
}
크레이트 내에서만 공개하기
크레이트 내부에서는 모듈을 자유롭게 참조하고 싶지만 외부에는 공개하고 싶지 않은 경우가 있습니다. 그럴 때는 pub(crate)
를 사용합니다.
// src/module_a.rs
pub(crate) fn greet() {
println!("Hello from module_a");
}
이렇게 하면 같은 크레이트 내에서는 module_a
의 greet
함수를 사용할 수 있지만, 외부에서는 접근이 불가능합니다.
lib.rs
와 main.rs
lib.rs
와 main.rs
모두 동일한 방식으로 모듈을 선언하고 사용할 수 있습니다. 하지만 일반적으로는 lib.rs
를 통해 라이브러리로 구성하고 main.rs
에서는 이를 사용하는 방식으로 작성하는 것이 좋습니다.
// src/lib.rs
pub mod module_a;
pub fn greet_from_lib() {
println!("Hello from the library");
}
// src/main.rs
use rust_modules::greet_from_lib;
use rust_modules::module_a::greet;
fn main() {
greet_from_lib();
greet();
}
이렇게 lib.rs
를 통해 라이브러리로 모듈을 구성하면 자연스럽게 API 설계가 가능해지고 코드 구조가 깔끔해집니다.
요약
lib.rs
와main.rs
가 가장 중요한 루트 파일입니다.mod
를 사용하여 모듈을 선언하고,pub mod
를 사용하여 상위 모듈에도 공개합니다.crate
,self
,super
등의 예약어를 통해 모듈 경로를 지정합니다.pub use
를 사용하여 모듈을 리네임하거나 공개 범위를 조절할 수 있습니다.
참고 문서
Rust 모듈 시스템에 대해 자세히 알고 싶다면 공식 문서를 참고하세요.
'Rust' 카테고리의 다른 글
Rust 강좌 2. 프라임 - 소수와 친해지기 (4) | 2024.08.08 |
---|---|
Rust 강좌 1. Cargo와 crates.io (0) | 2024.08.05 |
Serde를 활용한 다양한 JSON 열거형 처리 방법 및 주의점 (0) | 2024.05.19 |
Rust 난수 생성 완벽 가이드 - rand 크레이트 사용법 (0) | 2024.05.17 |
Rust로 좋은 CLI 프로그램 작성하기 (0) | 2024.03.30 |