모듈과 크레이트로 코드 구성하기

RustBeginner
지금 연습하기

소개

Rust 프로그램이 커지면 모든 타입과 함수를 하나의 파일에 넣어 두었을 때 각 요소의 역할을 파악하기 어려워집니다. CLI Lab에서 라이브러리와 바이너리를, testing Lab에서 테스트 모듈을 이미 살펴보았습니다. 이번 Lab에서는 준비된 예제를 하나의 모델로 연결합니다. **패키지(package)**는 Cargo가 빌드하는 프로젝트이고, **크레이트(crate)**는 컴파일 단위이며, **모듈(module)**은 크레이트 안의 이름을 구성합니다.

작은 식료품 저장고 보고서 패키지를 구성합니다. 파일과 대부분의 구현 코드는 이미 준비되어 있으므로, 각 단계에서는 하나의 경계에 집중합니다. 라이브러리 크레이트에서 항목을 공유하고, 파일 모듈을 선언하며, 작은 public API를 바이너리 크레이트로 가져옵니다.

크레이트 사이에서 함수 공유하기

이 단계에서는 패키지에 포함된 두 크레이트를 확인하고, 라이브러리의 함수 하나를 바이너리에서 사용할 수 있도록 설정합니다.

준비된 Cargo 패키지 디렉터리로 이동합니다.

cd /home/labex/project/pantry-report

Cargo.tomlpantry-report라는 하나의 패키지를 설명합니다. Cargo는 src/lib.rs를 라이브러리 크레이트의 루트로, src/main.rs를 바이너리 크레이트의 루트로 인식합니다. 두 파일은 같은 패키지에 속하지만 서로 별도의 크레이트로 컴파일됩니다.

두 크레이트 루트의 내용을 확인합니다. sed -n 명령은 파일을 수정하지 않고 지정한 줄 범위만 출력합니다.

sed -n '1,120p' src/lib.rs
sed -n '1,120p' src/main.rs

바이너리는 라이브러리 크레이트 경로를 통해 report_title을 가져옵니다. 하이픈이 포함된 패키지 이름은 Rust 소스에서 밑줄이 포함된 크레이트 이름으로 바뀝니다. 따라서 pantry-reportpantry_report가 됩니다.

준비된 반환 타입 &'static str은 프로그램 전체 실행 기간 동안 유효한 문자열 리터럴을 빌린다는 뜻입니다. 'static 표시는 수명(lifetime) 주석입니다. 명시적 수명 규칙은 이 초급 과정의 범위에 포함되지 않으므로, 이번 Lab에서 학습자가 수명을 이해하거나 작성할 필요는 없습니다.

항목은 pub으로 표시하지 않는 한 해당 모듈에서 비공개입니다. 라이브러리 루트를 엽니다.

nano src/lib.rs

함수 선언만 다음과 같이 변경합니다.

fn report_title() -> &'static str {

다음 코드로 변경합니다.

pub fn report_title() -> &'static str {

pub을 지정하면 이 함수가 라이브러리 크레이트의 public 인터페이스에 포함됩니다. Ctrl+O를 눌러 저장하고, Enter를 누른 다음 Ctrl+X로 종료합니다.

최종 실행 파일을 만들지 않고 두 크레이트의 타입을 검사하려면 cargo check를 사용합니다.

cargo check

그런 다음 바이너리 크레이트를 실행합니다.

cargo run --quiet

출력은 다음과 같습니다.

Pantry Report

이 결과는 바이너리가 크레이트 경계를 넘어 public 라이브러리 함수를 호출할 수 있음을 보여 줍니다.

파일 모듈 선언하기

이 단계에서는 준비된 inventory.rs 파일을 라이브러리 크레이트의 모듈 트리에 연결합니다.

Rust 소스 파일은 파일이 존재한다는 이유만으로 컴파일되지 않습니다. 크레이트 루트에서 해당 모듈을 선언해야 합니다. inventory라는 이름으로 선언하면 Rust는 src/inventory.rs를 찾아 그 안의 항목을 inventory::... 경로 아래에 배치합니다.

준비된 모듈 파일의 내용을 확인합니다. 앞에서처럼 sed -n '1,200p'-n으로 자동 출력을 억제하고, 1,200p로 지정한 줄 범위만 출력합니다.

sed -n '1,200p' src/inventory.rs

#[derive(Debug)] 줄은 Item에 표준 디버그 형식 지원을 생성하도록 Rust에 요청합니다. 현재 프로그램은 이 기능에 의존하지 않으므로, 새로운 요구 사항이 아니라 미리 준비된 메타데이터로 이해하면 됩니다. Item 타입과 해당 필드, describe는 public입니다. availability 헬퍼에는 pub이 없으므로 모듈 내부에서만 사용할 수 있습니다. describe는 이 private 헬퍼를 호출할 수 있지만, 모듈 외부의 호출자는 public 결과만 받습니다.

라이브러리 루트를 엽니다.

nano src/lib.rs

// MODULE_DECLARATION을 다음 코드로 바꿉니다.

pub mod inventory;

첫 번째 pub은 라이브러리 크레이트를 통해 모듈을 노출합니다. mod inventory; 부분은 파일을 모듈 트리에 연결합니다. Nano에서 저장하고 종료한 다음 패키지를 검사합니다.

cargo check
Finished `dev` profile ...

검사가 성공하면 Rust가 src/inventory.rs를 찾아 pantry_report::inventory로 컴파일했다는 뜻입니다.

Public API 가져와 사용하기

이 단계에서는 use 선언으로 모듈 항목을 바이너리 크레이트로 가져온 다음, 식료품 저장고 기록을 출력합니다.

가져오기 선언이 없으면 전체 경로는 pantry_report::inventory::Itempantry_report::inventory::describe입니다. use 선언은 항목을 이동하거나 복사하지 않고 현재 스코프에서 더 짧은 이름을 사용할 수 있게 합니다.

바이너리 소스 파일을 엽니다.

nano src/main.rs

// INVENTORY_IMPORT를 다음 그룹 가져오기로 바꿉니다.

use pantry_report::inventory::{describe, Item};

중괄호를 사용하면 같은 경로 접두사를 공유하는 두 항목을 묶을 수 있습니다. 다음으로 // INVENTORY_REPORT를 다음 코드로 바꿉니다.

    let lentils = Item {
        name: String::from("lentils"),
        quantity: 4,
    };
    println!("{}", describe(&lentils));

타입과 두 필드가 모두 public이므로 바이너리에서 Item을 생성할 수 있습니다. 생성한 항목을 describe에 빌려주면, 이 public 함수가 자체 모듈 내부의 private 함수인 availability를 호출합니다.

Nano에서 저장하고 종료한 다음 패키지를 실행합니다.

cargo run --quiet
Pantry Report
lentils: 4 jars (stocked)

두 줄의 출력은 작은 public API를 통해 크레이트 수준의 공유와 모듈 수준의 코드 구성이 모두 작동함을 보여 줍니다.

요약

라이브러리 크레이트와 바이너리 크레이트가 포함된 하나의 Cargo 패키지를 다뤘습니다. 라이브러리 항목을 public으로 만들고, 파일 모듈을 선언했으며, 크레이트 및 모듈 경로를 따라갔습니다. 또한 use로 경로를 짧게 만들고, 작은 public API 뒤에 구현 헬퍼를 private으로 유지했습니다.