crate와 라이브러리

러스트에서 모든 코드는 하나 이상의 크레이트(crate)로 구성된다. 크레이트는 러스트가 컴파일을 수행할 때의 기본 단위이며, 크레이트 하나가 곧 하나의 컴파일 결과물이 된다. 보통 실행 가능한 바이너리를 만드는 경우에는 바이너리 크레이트(binary crate), 재사용 가능한 라이브러리를 만드는 경우에는 라이브러리 크레이트(library crate)라는 용어를 사용한다. 크레이트는 서로 독립적으로 컴파일되고, 필요한 경우 다른 크레이트에 의해 로드되어 기능을 제공한다.

러스트 프로젝트를 만들 때 많이 사용하는 도구인 Cargo를 통해 크레이트를 손쉽게 생성할 수 있다. Cargo를 사용해 기본적인 바이너리 프로젝트를 생성하면 Cargo.toml 파일과 src/main.rs 파일이 만들어지는데, 이는 바이너리 크레이트를 위한 기본 구조이다. 반면 라이브러리 프로젝트를 생성하면 src/lib.rs 파일이 만들어지고, 이는 라이브러리 크레이트의 진입점 역할을 한다. 이때 Cargo.toml[lib] 섹션에서 필요한 설정을 지정하거나, 별도로 설정하지 않으면 디폴트로 src/lib.rs가 라이브러리 크레이트의 루트가 된다.

라이브러리는 다른 크레이트에서 사용할 코드를 모아 놓는 방식이다. 예를 들어, 여러 함수, 모듈, 구조체, 열거형 등을 라이브러리로 묶어 놓으면 다른 크레이트(바이너리든 라이브러리든)에서 이 라이브러리를 의존성으로 추가해 재사용할 수 있다. 이때 Cargo 환경에서 라이브러리를 사용하려면 Cargo.toml[dependencies] 섹션에 해당 라이브러리의 정보를 추가한다. 만약 crates.io에 등록된 라이브러리라면 버전만 명시하면 되지만, 로컬 경로에 있는 라이브러리라면 path 속성을 사용하여 의존성을 지정한다.

러스트의 모듈 시스템과 함께 라이브러리를 구성하면, 여러 개의 파일로 이루어진 큰 프로젝트를 체계적으로 관리할 수 있다. 라이브러리의 루트 파일인 lib.rs에서는 공개(pub) 범위를 설정해 다른 크레이트에서 사용할 것과 내부적으로만 사용할 것을 구분하며, 모듈 경로를 통해 코드 구조를 설계한다. 예를 들어, 라이브러리에서 제공하려는 핵심 API나 자료구조는 pub fn, pub struct 등으로 공개 범위를 설정한다. 반면 내부 구현 세부 사항은 공개 범위를 설정하지 않아 외부에서 접근할 수 없도록 캡슐화한다.

라이브러리 크레이트를 생성할 때는 Cargo 명령어를 사용한다. 예를 들어, 아래와 같이 새 프로젝트를 생성하면서 라이브러리로 초기화할 수 있다.

cargo new my_library --lib

이 명령을 실행하면 my_library 디렉터리가 생기고, 내부에는 Cargo.toml 파일과 src/lib.rs 파일이 생성된다. Cargo.toml은 라이브러리에 대한 메타데이터를, src/lib.rs는 라이브러리의 루트 파일을 담당한다. 예시로, src/lib.rs에 간단한 함수를 작성하고 이를 외부에 공개하려면 다음과 같이 코드를 작성할 수 있다.

pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

이렇게 작성된 라이브러리는 다른 크레이트에서 사용될 수 있다. 만약 이 라이브러리를 로컬 경로로 의존하고 싶다면 다른 프로젝트의 Cargo.toml 파일에 다음과 같이 작성한다.

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

그리고 의존성을 추가한 프로젝트에서 아래와 같이 사용하면 된다.

use my_library::greet;

fn main() {
    let message = greet("Rust");
    println!("{}", message);
}

라이브러리를 여러 개로 나누어 관리할 수도 있는데, 이는 모듈화와 재사용성 측면에서 큰 이점을 제공한다. 특정 기능 그룹을 하나의 라이브러리로 묶고, 해당 라이브러리를 다른 프로젝트에서 재사용할 수 있게 만들면 유지 보수성이 높아지고 중복 코드를 줄일 수 있다.

라이브러리와 바이너리가 동시에 필요한 경우에는 한 프로젝트 안에서 [lib][[bin]] 섹션을 모두 정의해 라이브러리와 실행 가능한 바이너리를 함께 구성할 수도 있다. 이때 src/lib.rs에는 라이브러리용 코드를, src/main.rs나 별도의 파일에는 바이너리용 코드를 담으면 된다. 바이너리 코드에서 라이브러리 코드를 재사용해 구현하면, 동일한 기능을 외부에 공개하면서 동시에 독립 실행 파일을 배포할 수 있다는 장점이 있다.

러스트 컴파일러는 라이브러리 크레이트를 컴파일할 때, 최종 결과물로 .rlib 또는 .so, .dll, .dylib 등의 형태를 생성한다. .rlib는 러스트 컴파일러 전용의 정적 라이브러리 형식이고, .so, .dll, .dylib 등은 운영체제 별 동적 라이브러리 형식이다. Cargo에서는 이런 빌드 과정을 자동으로 처리하므로, 개발자는 Cargo 명령만으로 손쉽게 라이브러리를 빌드하고 배포할 수 있다.

러스트에서 라이브러리를 잘 관리하려면 크레이트의 최상위 레벨에서 공개와 비공개의 경계를 명확히 설정하고, 모듈 시스템을 통해 내부 기능을 적절히 분할하는 것이 중요하다. 특히 다른 크레이트에서 노출되어야 하는 API라면 반드시 pub 키워드를 사용해 노출해야 하며, 구현 세부사항은 노출하지 않음으로써 인캡슐레이션을 지킨다. Cargo를 통한 버전 관리와 의존성 관리를 적절히 활용하면, 여러 라이브러리를 한 프로젝트에서 동시에 사용하는 것도 수월해진다.

정리하자면, 러스트에서 crate는 컴파일의 단위이며, 라이브러리 crate는 재사용 가능한 코드를 모아 두고 이를 외부에서 가져다 쓸 수 있도록 관리하는 용도다. Cargo를 통해 쉽게 생성하고 관리할 수 있으며, 모듈 시스템을 활용해 코드를 체계적으로 구조화한다. 이러한 크레이트와 라이브러리의 개념은 러스트 생태계에서 굉장히 중요한 요소이므로, 프로젝트를 진행하면서 자연스럽게 익혀 두면 대규모 개발에서도 효율적이고 안정적인 설계를 구현할 수 있다.

Last updated