본문 바로가기

카테고리 없음

[Rust] 파일 분할, mod, use, lib.rs

 모든 프로그램이 그렇듯이 프로그램이 커지면 하나의 파일로 관리하기가 불가능해집니다.

 따라서 파일을 분리하고 서로 연결하는 과정이 필요합니다.

 

cargo로 새 프로젝트를 구성합니다.

cargo new greeting
cd greeting

 

 예로 인사 프로그램을 만들어 봅시다.

// main.rs
fn hello_ko() {
    println!("안녕하세요!");
}

fn hello_en() {
    println!("Hello!");
}

fn main() {
    hello_ko();
    hello_en();
}

 

1. mod는 코드를 구역으로 나눕니다.

// main.rs
mod korean {
    pub fn hello_ko() {
        println!("안녕하세요!");
    }
}

mod english {
    pub fn hello_en() {
        println!("Hello!");
    }
}

fn main() {
    korean::hello_ko();
    english::hello_en();
}

 

 나중에 기능을 추가할 때를 대비해 언어별로 관리하는게 편리할 것입니다. 따라서 함수를 korean과 english 모듈로 나누겠습니다.

 mod 모듈이름 {...}을 사용하면 C++의 namespace처럼 코드의 구역을 나눌 수 있습니다. 호출도 C++와 비슷하게 모듈이름::함수이름으로 호출합니다. 이때 모듈 내부에 선언한 것은 pub를 선언해야 외부에서 접근 가능합니다.

 

2. rust는 mod를 기준으로 파일을 나누고, 찾습니다.

// main.rs
mod korean;
mod english;

fn main() {
    korean::hello();
    english::hello();
}

 

 본문 없이 mod 모듈이름;을 작성하면 rust는 해당 모듈을 외부의 파일에서 찾고, 그 내용을 가져옵니다.

 이때 rust는 mod가 작성된 파일과 같은 위치에 모듈이름.rs 혹은 모듈이름 폴더 내부에 mod.rs 둘 중 하나를 찾습니다.

 (만약 한 모듈에 두 가지 방식이 동시에 있다면 작동하지 않습니다.)

 

// korean/mod.rs
pub fn hello_ko() {
    println!("안녕하세요!");
}

// english.rs
pub fn hello_en() {
    println!("Hello!");
}

// main.rs
mod korean;  //  <-  korean/mod.rs의 내용을 여기 넣는다.
mod english;

fn main() {
    korean::hello_ko();
    english::hello_en();
}

 

 korean은 폴더 방식으로, english는 파일 방식으로 분할했습니다. 만약 모듈이 단순하다면 파일 방식이, 모듈 내에서 다시 모듈을 나눌 필요가 있다면 폴더 방식이 편리합니다. 이렇게 나눈 코드는 위에서 하나의 파일에 쓴 코드와 결과적으로 동일합니다.

 

3. use는 모듈 계층을 축약합니다.

// main.rs
mod korean;
mod english;

use korean::hello_ko;
use english::hello_en;

fn main() {
    hello_ko(); // korean::hello_ko와 동일
    hello_en();
}

 

 use가 없다면 특정 함수를 호출하기 위해 std::io::stdin() 처럼 구조를 전부 작성해야 합니다. 이때 use를 사용하면 해당 모듈, 함수 등을 축약해 작성할 수 있습니다.

 

4. lib.rs와 main.rs는 별개입니다.

 어떤 프로젝트는 main.rs와 lib.rs를 동시에 가지고 있습니다. main.rs는 실행 가능한 프로그램, lib.rs는 라이브러리를 만들 때 사용되고, 둘은 직접 연결되지 않습니다.

 

 우리가 만든 greeting 프로그램을, 인사 함수를 가지는 greeting 라이브러리와 그걸 쓰는 greeting 프로그램으로 나눠보겠습니다. 프로젝트에 lib.rs를 추가하고 작성합니다.

// lib.rs
pub mod korean; // <- korean/mod.rs의 내용을 여기 추가
pub mod english;

// main.rs
mod korean; // <- korean/mod.rs의 내용을 여기 추가
mod english;

use korean::hello_ko;
use english::hello_en;

fn main() {
    hello_ko(); // 라이브러리의 함수가 아님
    hello_en();
}

 

 lib.rs에 함수가 들어가고, 프로그램은 작동하지만, 올바른 방법이 아닙니다.

 mod는 해당 파일의 내용을 해당 위치에 붙이는 식으로 작동합니다. 그렇기에 lib.rs의 hello_ko()와 main.rs의 hello_ko()는 같은 코드가 두 번 들어간, 서로 다른 함수가 됩니다. main.rs가 라이브러리의 코드를 사용하려면 모듈 이름을 통해 함수를 호출해야 합니다.

 

// main.rs
fn main() {
    greeting::korean::hello_ko(); // greeting 라이브러리의 함수를 사용
    greeting::english::hello_en();
    //crate::english::hello_en(); // 작동x
}

 

 greeting은 처음에 cargo로 만든 프로젝트 이름입니다. std를 사용할 때처럼 모듈 이름을 명시해서 내부의 함수를 사용할 수 있습니다.

 main.rs에서 라이브러리를 호출할 때 자신의 최상위를 지칭하는 crate를 사용할 수 없습니다. main.rs는 lib.rs와 별개이기 때문에 greeting 라이브러리에 속하지 않습니다. main.rs에서 crate는 main.rs 자신을 뜻합니다.

 

잘 설명된 글:

Rust modules confusion when there is main.rs and lib.rs - Stack Overflow

 

번외. prelude

 원하는 함수를 호출할 때마다 모든 계층 구조를 외워야 한다면 불편할 겁니다. 그렇기에 rust는 기본 라이브러리에서 자주 사용하는 기능에 바로 접근할 수 있게 prelude라는 부분을 사전에 정의합니다. 예를 들어 Option<T>는 표준 라이브러리의 std::option::Option에 있지만, 바로 사용할 수 있습니다.

 

 동일한 맥락으로, 라이브러리 작성자도 자주 사용하는 기능에 빠르게 접근할 수 있도록 prelude를 만들기도 합니다.

// lib.rs
pub mod korean;
pub mod english;

pub mod prelude {
    pub use super::korean::hello_ko;
    pub use super::english::hello_en;
}

// main.rs
use greeting::prelude::*;

fn main() {
    hello_ko();
    hello_en();
}

 

 라이브러리에 prelude란 모듈을 만들고 자주 사용되는 모듈에 use문을 추가합니다.

 사용자는 prelude::*를 use 해 내부의 use문을 따라 쓴 효과를 얻을 수 있습니다.

 prelude는 사용자가 내부의 복잡한 구조를 알 필요 없게 하고, 라이브러리 작성자가 원하는 대로 내부를 재구성해 노출할 수 있게 합니다.