Rust, 프로젝트 구조 설계

Jeonghak Cho·2025년 1월 30일

Rust

목록 보기
10/13

개요

시작은 러스트로 프로젝트 구조를 잡는 것이다. 우선 크레이트, 모듈,패키지, 워크 스페이스에 대한 개념 이해가 필요하다.

크레이트

Rust에서 크레이트(crate) 는 컴파일의 기본 단위이며, 라이브러리 또는 실행 가능한 바이너리를 구성하는 코드의 모음이다.

라이브러리와 크레이트 차이

라이브러리는 코드를 재사용할 수 있게 해주는 특정 기능을 미리 구현해 놓은 코드 모음이다. 수학 계산, 네트워크 통신, 파일 입출력 기능을 라이브러리로 미리 구현해 두어 재사용성을 높이고 개발 속도를 빠르게 할 수 있다.바이너리는 컴파일 후 실행 가능한 바이너리 파일이다.

라이브러리의 종류
:one: 표준 라이브러리 (std):
Rust에 기본으로 포함된 라이브러리로서 기본적인 데이터 구조, 파일 처리, 네트워크 통신 등 다양한 기능을 제공한다.
:two: 외부 라이브러리 (크레이트):
Rust 커뮤니티에서 만든 라이브러리다. crates.io라는 사이트에서 수많은 외부 라이브러리를 찾을 수 있다. cargo new --lib 라이브러리_이름 명령으로 직접 라이브러리 생성도 가능하다.

Rust에서는 모든 라이브러리는 크레이트지만, 모든 크레이트가 라이브러리는 아니다.
즉, 라이브러리는 크레이트의 한 유형일 뿐, 크레이트가 더 넓은 개념이다.

크레이트 특징

독립성: 크레이트는 독립적으로 컴파일되고 배포될 수 있다.
의존성 관리: 크레이트는 다른 크레이트를 의존성으로 사용할 수 있다. 예를 들어, Cargo.toml 파일에 의존성을 추가하여 외부 크레이트를 사용할 수 있다.
배포: 크레이트는 crates.io에 배포되어 다른 개발자들이 사용할 수 있다.
종류: 크레이트는 크게 두 가지 유형으로 나뉜다.
:one: 라이브러리 크레이트 (Library Crate)
라이브러리 크레이트는 다른 프로그램에서 사용할 수 있는 코드를 제공한다. 독립적으로 실행되지 않으며, 다른 크레이트나 프로젝트에서 의존성으로 사용된다.
예를 들어, serde는 JSON 직렬화/역직렬화를 제공하는 라이브러리 크레이트이다.
:two: 바이너리 크레이트 (Binary Crate)
바이너리 크레이트는 컴파일 후 실행 가능한 바이너리 파일을 생성한다. 일반적으로 main.rs 파일을 진입점으로 하며, 프로그램의 실행 로직을 포함한다.

크레이트 사용법

라이브러리를 사용하려면 Cargo.toml 파일에 의존성을 추가해야 한다. 예를 들어, 내가 만든 lib.rs를 mylib로 등록하여 라이브러리로 사용하고 싶다면 Cargo.toml에 다음처럼 추가한다.

myapp/
├── Cargo.toml  # 워크스페이스 루트
├── src/
│   ├── main.rs  # 바이너리 크레이트
│   ├── lib.rs   # 라이브러리 크레이트
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"

[lib]
name = "mylib"
path = "src/lib.rs"

cargo run 으로 바이너리 main.rs 실행이 가능하고, cargo test로 lib.rs 내의 테스트 케이스를 실행할 수 있다.
바이너리에서 라이브러리를 사용하기 위해 use로 라이브러리를 불러 사용한다.

use mylib::add;
fn main() {
    let result = add(2, 2);
    assert_eq!(result, 4);
}

모듈(Module)

모듈은 크레이트 내부에서 코드를 논리적으로 구분한다. 모듈은 크레이트 안에 정의되며, 코드를 그룹화하고 네임스페이스를 제공하여 가독성과 유지보수성을 높인다.

모듈 특징

계층 구조: 모듈은 중첩될 수 있으며, 계층적으로 조직화할 수 있다.
가시성 제어: pub 키워드를 사용하여 모듈 내의 항목(함수, 구조체, 열거형 등)을 외부에 공개할지 여부를 결정할 수 있다.
파일 분리: 모듈은 별도의 파일로 분리될 수 있다. 예를 들어, mod my_module;을 선언하면 my_module.rs 파일에서 모듈을 정의할 수 있다.

크레이트는 외부에서 재사용 가능한 코드를 제공하는 반면, 모듈은 내부적으로 코드를 관리하고 가독성을 높이는 데 초점이 맞춰져 있다.

myproject/
├── Cargo.toml
├── src/
│   ├── main.rs  // 메인 파일
│   ├── greetings.rs  // 모듈 파일
pub fn hello() {
    println!("Hello from greetings module!");
}
mod greetings;  // greetings.rs 파일을 모듈로 포함

fn main() {
    greetings::hello();
}

모듈과 라이브러리의 차이

비교모듈(Module)라이브러리(Library)
역할크레이트 내부 코드 조직화다른 프로젝트에서 가져다 사용 가능
파일 구조mod.rs, mod name;lib.rs (크레이트 진입점)
빌드 방식실행 파일과 함께 빌드됨cargo build 시 .rlib 파일 생성
사용 예시mod greetings;use mylibrary;

패키지

패키지(Cargo.toml로 정의됨)는 Cargo.toml 파일을 통해 프로젝트의 메타데이터와 의존성을 관리한다. 패키지는 크레이트를 조직화하고, 빌드, 테스트, 배포를 쉽게 할 수 있도록 돕는다.

패키지 생성 및 관리

패키지는 cargo new 명령어를 통해 생성할 수 있다. 예를 들어, mypackage라는 이름의 패키지를 생성하려면 다음과 같이 명령어를 실행한다.

cargo new mypackage

Cargo.toml: 패키지의 메타데이터와 의존성을 정의한다.
src/main.rs: 바이너리 크레이트의 진입점이다.

mypackage/
├── Cargo.toml
├── src/
│   └── main.rs
└── .gitignore

빌드단위, 컴파일 단위의 차이

Rust에서 빌드 단위(Build Unit)컴파일 단위(Compilation Unit) 는 개념적으로 다르다. Rust에서는 패키지를 빌드 단위로, 크레이트를 컴파일 단위로 관리한다.

개념설명
빌드 단위Cargo가 관리하는 패키지(Crate & Dependencies) 수준의 빌드 단위
컴파일 단위Rust 컴파일러(rustc)가 개별적으로 컴파일하는 Crate 또는 Module 수준

패키지 단위로 Cargo가 한 번에 빌드한다. 패키지(Cargo.toml로 정의됨)는 하나 이상의 크레이트(Crate)를 포함할 수 있다. 모든 크레이트와 그 종속성(Dependencies)을 하나로 묶어 빌드한다.

📁 빌드 단위 예제 (패키지)

mypackage/
├── Cargo.toml  # 패키지 정의 (빌드 단위)
├── src/
│   ├── lib.rs  # 라이브러리 크레이트
│   ├── main.rs # 바이너리 크레이트
└── target/     # 빌드 결과물 (빌드 단위 기준)

Rust 컴파일러(rustc)가 독립적으로 컴파일하는 단위다.일반적으로 각각의 크레이트(Crate) 가 독립적인 컴파일 단위다.크레이트 내에서 개별 모듈(mod)은 하나의 크레이트 내부에서만 관리 되며 별도로 컴파일되지 않는다.

📁 컴파일 단위 예제 (크레이트)

mypackage/
├── Cargo.toml
├── src/
│   ├── lib.rs  # 하나의 크레이트 (컴파일 단위)
│   ├── main.rs # 별도의 크레이트 (컴파일 단위)
│   ├── module.rs  # 단순 모듈 (독립적 컴파일 단위 X)

빌드단위, 컴파일 단위 결과

:one: cargo build → 빌드 단위

cargo build
  • 패키지 전체가 한꺼번에 빌드됨 (Cargo.toml 기준).
  • target/ 폴더가 생성되고 빌드 결과물이 저장됨.

:two: rustc → 컴파일 단위

rustc src/main.rs
  • main.rs만 개별적으로 컴파일됨.
  • Cargo 없이도 크레이트 단위로 개별 컴파일 가능.

워크스페이스

워크스페이스(Workspace) 는 Rust에서 여러 관련된 패키지(크레이트)를 하나의 공통된 환경에서 관리할 수있도록 한다. 워크스페이스를 사용하면 여러 패키지를 동시에 빌드하고, 테스트하고, 의존성을 공유할 수 있다. 이는 대규모 프로젝트나 여러 하위 프로젝트로 구성된 프로젝트를 관리할 때 유용하다.

워크스페이스 특징

여러 개의 패키지를 포함하는 하나의 프로젝트

  • 각 패키지는 독립적인 크레이트(라이브러리 또는 바이너리)를 가짐.
  • 공통된 종속성을 공유하여 빌드 시간을 줄일 수 있음.

루트 Cargo.toml에서 전체 워크스페이스를 정의

  • Cargo.toml에서 [workspace] 섹션을 사용하여 여러 패키지를 관리.
  • 개별 패키지는 자신의 Cargo.toml을 가질 수 있음.

공유된 target/ 디렉터리

  • 워크스페이스 내 모든 패키지가 하나의 target/ 디렉터리를 공유하므로 빌드 속도가 최적화됨.

📁 워크스페이스 구조

.
├── Cargo.lock
├── Cargo.toml
├── mybin
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── mylib
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── src
│   ├── lib.rs
│   └── main.rs

myapp 워크스페이스가 복수의 패키지 mylib, mybin 가 있다.

[workspace]
members = [
    "mylib",
    "mybin"
]

하위 패키지에서 상대 위치를 사용하여 다른 패키지를 참조할 수 있다.

[dependencies]
mylib = { path = "../mylib" }

cargo build -p mybin 처럼 패키지 별로 빌드하거나 cargo build로 전체 빌드할 수 있다.

응용

복수의 바이너리

📁 크레이트 구조

my_crate/
├── Cargo.toml
├── src/
│   ├── lib.rs       # 라이브러리 크레이트 (선택적)
│   ├── main.rs      # 기본 바이너리 크레이트
│   └── bin/
│       ├── bin1.rs # 추가 바이너리 크레이트 1
│       └── bin2.rs # 추가 바이너리 크레이트 2
  • bin1.rs
fn main() {
    println!("Hello, world! - bin1");
}
  • bin2.rs
fn main() {
    println!("Hello, world! - bin2");
}

cargo run --bin bin1,cargo run --bin bin2 형태로 개별 바이너리를 실행할 수 있다.

결론

라이브러리와 모듈 차이, 크레이트와 패키지 차이, 프로젝트와 워크스페이스 개념 등 을 확실히 이해하고 넘어가야 한다. 대규모 시스템 구축을 위한 기능별 분리를 통해 전체 프로젝트의 효율을 높일 수 있다.

  • 워크스페이스는 복수의 패키지를 가진다.
  • 워크세프이스의 패키지는 전체 또는 개별로 패키징이 가능하다.
  • 한 패키지는 복수의 크레이트를 가질 수 있다.
  • 크레이트는 라이브러리이거나 바이너리 일 수 있다.
  • 라이브러리는 크레이트 내부에서 정의되거나 외부의 크레이트 형태로 구성할 수 있다.
  • 크레이트는 복수의 바이너리를 가질 수 있으며 개별 바이너리를 독립적으로 실행할 수 있다.
  • 크레이트는 단 하나의 라이브러리를 가질 수 있다.
  • 하나의 패키지에 여러개의 라이브러리(Crate)를 가지는 것은 가능하다.

0개의 댓글