본문으로 건너뛰기

안동민 개발노트

본문 시작

패키지와 크레이트

Cargo 패키지의 target이 크레이트 루트를 선택하고, 각 크레이트가 모듈 트리와 공개 경로를 만드는 구조를 구분합니다.

프로젝트가 커지면 “폴더가 어디에 있는가”만으로는 코드의 경계를 설명할 수 없습니다. Cargo가 관리하는 패키지, 컴파일러가 한 번에 처리하는 크레이트, 크레이트 안의 모듈, 아이템을 가리키는 경로를 구분해야 합니다.

한 패키지는 여러 바이너리 크레이트와 최대 하나의 라이브러리 크레이트를 가질 수 있습니다. 함께 발전하는 여러 패키지는 13장 ‘Cargo 작업공간’에서 다룰 workspace로 묶습니다.

Cargo manifest가 package와 target을 설명하고, 각 target이 별도 crate root와 module tree를 만들며, path와 visibility가 아이템의 사용 경계를 정하는 네 단계 계약

Rust · PACKAGE TO PATH

manifest에서 공개 경로까지 경계를 한 단계씩 읽는다

Package, crate, module, path는 같은 이름의 폴더 단계가 아닙니다. Cargo가 target을 고르고, 각 target의 crate root에서 컴파일러의 module tree가 시작됩니다.

  1. Package · Cargo가 관리하는 기능 묶음

    Cargo.toml이 package metadata, edition, dependency와 target 설정을 설명합니다.

  2. Target → crate · 별도 컴파일 경계

    Library target은 최대 하나, binary target은 여러 개 둘 수 있으며 각 target은 하나의 crate root에서 별도 crate를 만듭니다.

  3. Crate root → module tree · 한 크레이트의 구조

    src/lib.rssrc/main.rs의 루트 모듈에서 mod 선언을 따라 아이템과 하위 모듈을 구성합니다.

  4. Path + visibility · 이름과 공개 표면

    crate::garden 같은 경로가 아이템을 가리키고, pub이 접근 범위를 열며, use는 스코프에 단축 이름을 만듭니다.

Dependency boundary

[dependencies]use는 역할이 다르다

Manifest 선언이 Cargo의 dependency graph를 바꿉니다. use는 이미 접근 가능한 경로의 이름만 현재 스코프에 가져옵니다.

Workspace boundary

Package 하나와 여러 package의 workspace를 구분한다

Package는 자체 target을 제공하고, workspace는 함께 개발하는 여러 package의 lockfile과 출력 디렉터리를 조정합니다.

소스 파일이 늘었다고 crate가 자동으로 늘지는 않습니다. 먼저 manifest의 target을 찾고, 각 crate root 안에서는 mod 선언과 공개 경로를 따라갑니다.


패키지, 크레이트, 모듈, 경로는 서로 다른 경계다

Rust의 모듈 시스템은 다음 개념을 함께 사용합니다.

  • 패키지(package): 크레이트를 빌드·테스트·공유하는 Cargo 단위입니다. 일반 패키지의 Cargo.toml에는 이름, 버전, edition, dependency와 target 설정이 들어갑니다.
  • 크레이트(crate): rustc가 한 번의 컴파일에서 고려하는 코드 단위입니다. 하나의 크레이트는 하나의 라이브러리 또는 실행 가능한 프로그램을 만듭니다.
  • 모듈(module): 한 크레이트 안에서 아이템을 트리로 조직하고 스코프와 공개 범위를 나눕니다. 모듈은 다른 파일에서 불러올 수 있지만, 파일마다 새 크레이트가 되는 것은 아닙니다.
  • 경로(path): crate::garden::vegetables::Asparagus처럼 모듈 트리 안의 아이템을 가리키는 이름입니다.

[dependencies]가 Cargo의 dependency graph에 의존할 패키지를 추가합니다. 반면 use는 이미 접근 가능한 경로의 이름을 현재 스코프에 가져올 뿐 dependency를 설치하거나 선언하지 않습니다.


크레이트와 크레이트 루트

크레이트 루트(crate root) 는 컴파일러가 시작점으로 받는 소스 파일이며 그 크레이트의 루트 모듈을 구성합니다. rustc는 한 번 호출될 때 하나의 입력 소스 파일에서 시작해 하나의 출력 크레이트를 만들고, mod 선언을 따라 같은 크레이트의 다른 모듈 파일을 읽을 수 있습니다.

학습 단계에서는 크레이트를 두 역할로 나눠 볼 수 있습니다.

  • 바이너리 크레이트는 실행 파일을 만들며 실행 시작점을 정의하는 main 함수가 필요합니다.
  • 라이브러리 크레이트는 실행 파일 대신 다른 코드가 사용할 기능과 공개 API를 제공합니다.

하나의 패키지 안에서도 각 library·binary target은 별도의 crate root에서 시작하므로 별도 크레이트로 컴파일됩니다. Cargo는 example, integration test, benchmark target도 다룰 수 있지만, 이 절에서는 패키지의 주 제품인 library와 binary target에 집중합니다.


패키지는 target을 설명하는 manifest를 가진다

패키지(package) 는 하나 이상의 크레이트로 기능을 제공하는 Cargo 번들입니다. 일반 패키지에는 크레이트를 어떻게 빌드할지 설명하는 Cargo.toml이 있고, 라이브러리 target은 최대 하나, 바이너리 target은 여러 개 둘 수 있습니다. 적어도 하나의 target이 있어야 실제 크레이트를 만들 수 있습니다.

Cargo 자체도 CLI를 만드는 바이너리 크레이트와 그 바이너리가 사용하는 라이브러리 크레이트를 한 패키지에서 제공하는 예입니다.

Cargo.toml은 package와 target의 의도를 기록하고, Cargo.lock은 dependency resolution 뒤 선택된 버전을 기록합니다. Cargo.lock이 새 크레이트나 module tree를 만드는 것은 아닙니다.


교재 edition으로 새 패키지 만들기

현재 Cargo에서 cargo new의 기본 edition은 2024입니다. 이 교재의 코드처럼 Rust 2021을 사용할 프로젝트라면 생성 시 edition을 명시합니다.

$ cargo new --edition 2021 my-project
     Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs

생성된 manifest에는 선택한 edition이 기록됩니다.

my-project/Cargo.toml
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"

[dependencies]

기본 binary package에서는 src/main.rs가 package 이름의 binary target과 crate root가 됩니다. Cargo.toml에 이 경로를 쓰지 않아도 Cargo의 target 자동 탐색 관례가 적용됩니다.

Cargo.toml과 src/lib.rs, src/main.rs, src/bin 파일 및 명시적 target 설정이 package의 library와 binary crate root로 해석되는 규칙표

Cargo · TARGET DISCOVERY

파일 경로가 target이 되는 관례와 예외를 함께 본다

Cargo는 source layout에서 target을 자동 탐색합니다. 각 발견된 library·binary target의 시작 파일은 별도 crate root이며, 일반 module 파일과 구분됩니다.

Package 파일과 Cargo target 해석
역할 Cargo가 만드는 경계 선택·실행 계약
Manifest Cargo.toml: package metadata, edition, dependency와 target override를 설명하는 manifest 그 자체는 crate root가 아님
Library src/lib.rs: package당 최대 하나의 library target과 library crate root cargo build --lib
Default binary src/main.rs: package 이름의 기본 binary target과 binary crate root cargo run --bin <package>
Extra binary src/bin/*.rs: admin.rs처럼 파일 stem을 이름으로 쓰는 추가 binary target과 별도 crate root cargo run --bin admin
Explicit target [lib] · [[bin]]: name, path 등으로 관례 밖 target을 명시 자동 탐색과 병행; 끄려면 autolib·autobins 설정
Cargo.toml · manifest
Package metadata, edition, dependency와 target override를 설명합니다.
Manifest 자체는 crate root가 아닙니다.
src/lib.rs · library
Package당 최대 하나의 library target과 library crate root입니다.
cargo build --lib
src/main.rs · default binary
Package 이름의 기본 binary target과 binary crate root입니다.
cargo run --bin <package-name>
src/bin/admin.rs · extra binary
admin target을 만들고 별도 crate root에서 컴파일합니다.
cargo run --bin admin
[lib] · [[bin]]
name, path 등으로 관례 밖 target을 명시합니다.
자동 탐색과 병행됩니다. 끄려면 autolib·autobins를 설정합니다.
Target roots

Library와 binary는 같은 package에서도 별도 crate다

src/lib.rssrc/main.rs가 함께 있으면 각각 컴파일됩니다. Binary는 library의 공개 API를 사용할 수 있습니다.

Module files

mod가 읽는 파일은 새 target이 아니다

src/garden.rs 같은 파일은 선언한 crate의 module tree에 들어가며 독립 실행 파일이나 library가 되지 않습니다.

현재 cargo new의 기본 edition은 2024입니다. 교재와 같은 Rust 2021 package는 --edition 2021로 생성하고 manifest의 edition 값을 확인합니다.


파일 관례가 target으로 해석되는 방식

  • src/lib.rs가 있으면 package 이름을 기본값으로 하는 library target과 crate root가 됩니다. Rust crate 식별자에서는 package 이름의 하이픈이 밑줄로 바뀝니다.
  • src/main.rs가 있으면 package 이름의 기본 binary target과 crate root가 됩니다.
  • src/bin/admin.rs 같은 파일은 파일 stem을 이름으로 하는 추가 binary target과 crate root가 됩니다.
  • 관례와 다른 이름이나 경로가 필요하면 Cargo.toml[lib] 또는 [[bin]]에서 name, path 등의 설정을 명시합니다.

Rust 2018 edition부터는 [lib][[bin]] target을 직접 적어도 파일 관례에 따른 자동 탐색이 계속 켜져 있습니다. 해당 종류를 수동 설정만으로 제한하려면 [package]autolib = false 또는 autobins = false를 명시합니다.

Binary target이 하나뿐이면 cargo run으로 선택할 수 있습니다. 여러 binary target이 있으면 cargo run --bin <이름>으로 고르거나 [package]default-run을 지정해야 합니다.

여기서 target은 Cargo가 빌드할 library·binary 같은 입력 단위입니다. 컴파일 산출물과 cache가 놓이는 target/ 디렉터리와는 다른 개념입니다.

따라서 src/main.rssrc/lib.rs가 모두 있으면 package 하나에서 binary crate 하나와 library crate 하나가 각각 컴파일됩니다. src/bin 아래 파일을 추가하면 binary crate가 더 생깁니다. 반대로 src/garden.rs처럼 mod garden;이 불러오는 파일은 기존 크레이트의 module tree에 들어갈 뿐 독립 target이 아닙니다.

바이너리 target은 같은 패키지의 library target이 공개한 API를 사용할 수 있습니다. 또한 normal dependency를 사용하려면 해당 package의 Cargo.toml에 직접 선언해야 합니다. 같은 workspace의 다른 package가 이미 사용한다는 사실만으로 dependency가 자동 상속되지는 않습니다.


Cargo 프로젝트를 읽는 순서

가장 가까운 Cargo.toml에서 [package]인지 [workspace]인지 확인합니다.

package 이름, edition, dependency와 명시적인 [lib]·[[bin]] target을 읽습니다.

명시되지 않은 target은 src/lib.rs, src/main.rs, src/bin 자동 탐색 관례로 찾습니다.

각 target의 crate root에서 mod 선언을 따라 module tree를 구성합니다.

pub 공개 범위와 crate::... 경로를 확인한 뒤 use가 현재 스코프에 어떤 이름을 가져오는지 읽습니다.

핵심은 폴더 이름을 그대로 실행 경계로 보는 것이 아닙니다. Manifest가 package와 target을 정하고, 각 target의 crate root가 별도 module tree를 시작하며, path와 visibility가 그 트리의 사용 가능한 공개 표면을 정합니다.