본문 바로가기

Crates.io 배포

문서화 주석과 공개 API·패키지 메타데이터를 준비해 크레이트를 배포하고 버전 갱신과 yank 절차를 익힙니다.

지금까지는 crates.io의 패키지를 프로젝트의 의존성으로 쓰기만 했지만, 직접 패키지를 배포(publish)해 다른 사람과 코드를 나눌 수도 있습니다. crates.io의 크레이트 레지스트리(registry)는 패키지의 소스 코드를 배포하므로, 주로 오픈 소스 코드를 호스팅합니다.

러스트와 Cargo에는 배포한 패키지를 사람들이 더 쉽게 찾고 쓰게 해 주는 기능이 있습니다. 먼저 그 기능 몇 가지를 이야기한 뒤, 패키지를 배포하는 방법을 설명합니다. 아래 그림은 이 글에서 다룰 배포 과정을 단계별로 보여 줍니다.

패키징·배포·yank의 변경 불가 경계

로컬 package/dry-run·별도 doctest 후 특정 버전을 publish하는 흐름과, 게시 이후 선택적인 yank 관리 행동을 비교한다. yank는 모든 게시의 필수 마지막 단계나 새 버전 게시의 선행 조건이 아니다.

RUST · PACKAGE RELEASE

새 버전 게시와 yank는 서로 다른 변경이다

본문의 공개 준비부터 게시까지를 먼저 따라갑니다. 수정은 새 버전을 만들고, yank는 게시 파일을 남긴 채 새 기본 선택을 제한합니다.

검토한 패키지를 특정 버전으로 게시하는 흐름API 문서 메타데이터 준비 후 로컬 패키징 검증과 별도 doctest를 거쳐 cargo publish로 특정 버전을 업로드한다.
API·문서·metadata이름·버전·license
연결 관계API·문서·metadata · 이름·버전·license → 로컬 패키징 확인 · package / dry-run
로컬 패키징 확인package / dry-run
연결 관계로컬 패키징 확인 · package / dry-run → 게시된 특정 버전 · cargo publish
게시된 특정 버전cargo publish
패키지 준비·로컬 검사·특정 버전 게시원 desktop의 세노드두진행간선이다. API문서와이름버전라이선스준비→로컬패키징과dry-run검사→특정버전의실제업로드로진행한다. dry-run은업로드하지않으며doctest는별도검사다. 아래yank 설명은새 기본해석제한이며 이세단계의필수네번째단계나게시파일삭제가아니다. 같은버전덮어쓰기·비밀삭제를보장하지않는다.
준비API · 문서 · metadata이름 · 버전 · license
연결 관계준비 · API · 문서 · metadata · 이름 · 버전 · license → 로컬 패키징 검사 · package / dry-run · doctest는 별도
로컬 패키징 검사package / dry-rundoctest는 별도
연결 관계로컬 패키징 검사 · package / dry-run · doctest는 별도 → 게시된 특정 버전 · cargo publish · 실제 업로드
게시된 특정 버전cargo publish실제 업로드

1 · 준비

이름·버전·description·license 또는 license-file, 공개 경로와 문서를 확인합니다.

2 · 패키징과 테스트

cargo package --list로 포함 파일을 확인하고 cargo publish --dry-run으로 업로드 없이 패키징/빌드를 확인합니다. 문서 테스트는 cargo test로 별도 실행합니다.

3 · 게시

cargo publish는 선택한 버전을 업로드합니다. 일반 게시 절차에서는 같은 이름·버전을 덮어쓰거나 삭제할 수 없습니다.

수정 · 새 버전 게시

기존 게시 파일을 바꾸는 대신 version을 높이고 다시 준비·검증·게시합니다. 수정 버전 게시에 yank가 선행할 필요는 없습니다.

yank · 새 기본 선택 제한

cargo yank --vers 1.0.1
cargo yank --vers 1.0.1 --undo

첫 명령은 새 의존성 해석의 기본 선택을 제한하고 둘째는 되돌립니다. 기존 lockfile의 버전과 코드 다운로드는 유지됩니다.

비밀 · 인증 토큰

토큰은 소스·로그에 포함하지 않고 credential provider나 cargo login의 입력으로 관리합니다. 유출 토큰은 즉시 폐기합니다. yank는 유출 파일을 없애지 않습니다.

포함 파일 · 문서 검증

cargo package --list
cargo publish --dry-run
cargo test

dry-run이 doctest 실행을 대신하지 않습니다. 패키징은 의존성 해석이나 다운로드를 필요로 할 수 있습니다.

yank에는 기존 lockfile과 명시적 cargo update --precise 선택 같은 예외가 있습니다. crates.io 관리 정책에 따른 조치는 일반 작성자의 게시 명령과 별개입니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명원래 가로 도표의 세 노드와 두 진행 간선이다. API 문서와 이름/버전/라이선스 준비→로컬 패키징과 dry-run 검사→특정 버전의 실제 업로드로 진행한다. dry-run은 업로드하지 않으며 doctest는 별도 검사다. 아래 yank 설명은 새 기본 해석의 제한이며 이 세 단계의 필수 네 번째 단계나 게시 파일 삭제가 아니다. 같은 버전 덮어쓰기나 비밀 삭제를 보장하지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
검증 준비

API·metadata를 확인하고 package/dry-run과 doctest를 분리한다.

  • 산출물: 패키지 검증
  • doctest: 별도 동작 검증
  • publish --dry-run은 업로드 없이 패키징·빌드를 검증한다. 문서 예제 실행은 별도 cargo test로 확인한다.
버전 공개 모델

publish 결과는 특정 crate 버전이며 수정은 새 버전으로 게시한다.

  • 대상: 특정 crate 버전
  • 변경 방식: 새 버전 추가
  • 이미 공개된 버전의 파일을 같은 번호로 교체하는 모델이 아니다.
별도 선택 · yank

게시 이후 선택적인 관리 행동은 해당 버전의 새 기본 선택을 제한한다.

  • 기존 파일: 삭제 아님
  • 기존 lockfile: 선택 자동 제거 아님
  • yank는 모든 게시의 필수 마지막 단계가 아니며 새 버전 게시에 먼저 실행할 필요가 없다. 기존 lockfile의 버전 선택과 코드 다운로드는 유지된다.

패키징·배포·yank의 변경 불가 경계

로컬 package/dry-run·별도 doctest 후 특정 버전을 publish하는 흐름과, 게시 이후 선택적인 yank 관리 행동을 비교한다. yank는 모든 게시의 필수 마지막 단계나 새 버전 게시의 선행 조건이 아니다.

RUST · PACKAGE RELEASE

새 버전 게시와 yank는 서로 다른 변경이다

본문의 공개 준비부터 게시까지를 먼저 따라갑니다. 수정은 새 버전을 만들고, yank는 게시 파일을 남긴 채 새 기본 선택을 제한합니다.

검토한 패키지를 특정 버전으로 게시하는 흐름API 문서 메타데이터 준비 후 로컬 패키징 검증과 별도 doctest를 거쳐 cargo publish로 특정 버전을 업로드한다.
API·문서·metadata이름·버전·license
연결 관계API·문서·metadata · 이름·버전·license → 로컬 패키징 확인 · package / dry-run
로컬 패키징 확인package / dry-run
연결 관계로컬 패키징 확인 · package / dry-run → 게시된 특정 버전 · cargo publish
게시된 특정 버전cargo publish
패키지 준비·로컬 검사·특정 버전 게시원 desktop의 세노드두진행간선이다. API문서와이름버전라이선스준비→로컬패키징과dry-run검사→특정버전의실제업로드로진행한다. dry-run은업로드하지않으며doctest는별도검사다. 아래yank 설명은새 기본해석제한이며 이세단계의필수네번째단계나게시파일삭제가아니다. 같은버전덮어쓰기·비밀삭제를보장하지않는다.
준비API · 문서 · metadata이름 · 버전 · license
연결 관계준비 · API · 문서 · metadata · 이름 · 버전 · license → 로컬 패키징 검사 · package / dry-run · doctest는 별도
로컬 패키징 검사package / dry-rundoctest는 별도
연결 관계로컬 패키징 검사 · package / dry-run · doctest는 별도 → 게시된 특정 버전 · cargo publish · 실제 업로드
게시된 특정 버전cargo publish실제 업로드

1 · 준비

이름·버전·description·license 또는 license-file, 공개 경로와 문서를 확인합니다.

2 · 패키징과 테스트

cargo package --list로 포함 파일을 확인하고 cargo publish --dry-run으로 업로드 없이 패키징/빌드를 확인합니다. 문서 테스트는 cargo test로 별도 실행합니다.

3 · 게시

cargo publish는 선택한 버전을 업로드합니다. 일반 게시 절차에서는 같은 이름·버전을 덮어쓰거나 삭제할 수 없습니다.

수정 · 새 버전 게시

기존 게시 파일을 바꾸는 대신 version을 높이고 다시 준비·검증·게시합니다. 수정 버전 게시에 yank가 선행할 필요는 없습니다.

yank · 새 기본 선택 제한

cargo yank --vers 1.0.1
cargo yank --vers 1.0.1 --undo

첫 명령은 새 의존성 해석의 기본 선택을 제한하고 둘째는 되돌립니다. 기존 lockfile의 버전과 코드 다운로드는 유지됩니다.

비밀 · 인증 토큰

토큰은 소스·로그에 포함하지 않고 credential provider나 cargo login의 입력으로 관리합니다. 유출 토큰은 즉시 폐기합니다. yank는 유출 파일을 없애지 않습니다.

포함 파일 · 문서 검증

cargo package --list
cargo publish --dry-run
cargo test

dry-run이 doctest 실행을 대신하지 않습니다. 패키징은 의존성 해석이나 다운로드를 필요로 할 수 있습니다.

yank에는 기존 lockfile과 명시적 cargo update --precise 선택 같은 예외가 있습니다. crates.io 관리 정책에 따른 조치는 일반 작성자의 게시 명령과 별개입니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명원래 가로 도표의 세 노드와 두 진행 간선이다. API 문서와 이름/버전/라이선스 준비→로컬 패키징과 dry-run 검사→특정 버전의 실제 업로드로 진행한다. dry-run은 업로드하지 않으며 doctest는 별도 검사다. 아래 yank 설명은 새 기본 해석의 제한이며 이 세 단계의 필수 네 번째 단계나 게시 파일 삭제가 아니다. 같은 버전 덮어쓰기나 비밀 삭제를 보장하지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
검증 준비

API·metadata를 확인하고 package/dry-run과 doctest를 분리한다.

  • 산출물: 패키지 검증
  • doctest: 별도 동작 검증
  • publish --dry-run은 업로드 없이 패키징·빌드를 검증한다. 문서 예제 실행은 별도 cargo test로 확인한다.
버전 공개 모델

publish 결과는 특정 crate 버전이며 수정은 새 버전으로 게시한다.

  • 대상: 특정 crate 버전
  • 변경 방식: 새 버전 추가
  • 이미 공개된 버전의 파일을 같은 번호로 교체하는 모델이 아니다.
별도 선택 · yank

게시 이후 선택적인 관리 행동은 해당 버전의 새 기본 선택을 제한한다.

  • 기존 파일: 삭제 아님
  • 기존 lockfile: 선택 자동 제거 아님
  • yank는 모든 게시의 필수 마지막 단계가 아니며 새 버전 게시에 먼저 실행할 필요가 없다. 기존 lockfile의 버전 선택과 코드 다운로드는 유지된다.

먼저 공개 API와 문서, 그리고 이름, 버전, description, license나 license-file 같은 메타데이터를 준비합니다. 그다음 cargo package --list로 패키지에 들어갈 파일을 확인하고, cargo publish --dry-run으로 실제로 올리지 않고 패키징과 빌드를 확인합니다. 문서 속 예제의 테스트는 dry-run이 대신해 주지 않으므로 cargo test로 따로 실행합니다. 마지막으로 cargo publish가 정한 버전을 업로드합니다. 고칠 것이 생기면 이미 올린 파일을 바꾸는 대신 버전을 올려 다시 배포하며, 그림의 마지막 단계인 yank는 이미 배포한 버전을 새 의존성이 고르지 않게 하는 별개의 관리 작업입니다.

유용한 문서화 주석 만들기

패키지를 정확하게 문서화하면 다른 사용자가 언제 어떻게 쓰는지 아는 데 도움이 되므로, 문서를 쓰는 데 시간을 들일 가치가 있습니다. 주석에서 두 개의 슬래시 //로 러스트 코드에 주석을 다는 방법을 이야기했습니다. 러스트에는 문서화 주석(documentation comment)이라는 특별한 주석도 있으며, 이 주석으로 HTML 문서를 만들 수 있습니다. 이 HTML은 크레이트가 어떻게 구현되었는지가 아니라 크레이트를 어떻게 쓰는지 알고 싶은 프로그래머를 위해, 공개 API 아이템의 문서화 주석 내용을 보여 줍니다.

문서화 주석은 슬래시 두 개 대신 세 개 ///를 쓰며, 텍스트를 꾸미는 마크다운(Markdown) 표기를 지원합니다. 문서화 주석은 문서화할 아이템 바로 앞에 둡니다. 다음은 my_crate라는 크레이트에 있는 add_one 함수의 문서화 주석입니다.

src/lib.rs
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let answer = my_crate::add_one(5);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

여기서는 add_one 함수가 무엇을 하는지 설명하고, Examples라는 제목의 절을 시작한 다음, add_one 함수를 쓰는 방법을 보여 주는 코드를 넣었습니다. cargo doc을 실행하면 이 문서화 주석으로 HTML 문서를 만들 수 있습니다. 이 명령은 러스트와 함께 배포되는 rustdoc 도구를 실행하고, 만든 HTML 문서를 target/doc 폴더에 넣습니다.

편하게 하려면 cargo doc --open을 실행합니다. 현재 크레이트의 문서와 크레이트가 의존하는 모든 크레이트의 문서를 HTML로 만들고, 그 결과를 웹 브라우저에서 엽니다. add_one 함수로 가면 다음과 같이 문서화 주석의 텍스트가 표시됩니다.

cargo doc가 생성한 add_one 함수의 HTML API 문서

자주 사용되는 절

앞의 예에서는 마크다운 제목 # Examples로 HTML에 Examples라는 제목의 절을 만들었습니다. 크레이트 작성자가 문서에서 흔히 쓰는 절은 다음과 같습니다.

절적는 내용
Panics문서화하는 함수가 패닉을 낼 수 있는 상황입니다. 프로그램이 패닉을 내지 않기를 바라는 호출자는 이런 상황에서 함수를 부르지 않도록 해야 합니다.
Errors함수가 Result를 돌려준다면, 어떤 종류의 오류가 어떤 조건에서 돌아오는지 적습니다. 호출자가 서로 다른 오류를 서로 다르게 다루는 코드를 쓰는 데 도움이 됩니다.
Safety함수를 부르는 것이 unsafe라면, 왜 안전하지 않은지와 호출자가 지켜야 하는 불변 조건을 설명하는 절을 둡니다. unsafe는 안전하지 않은 러스트에서 다룹니다.

대부분의 문서화 주석에 이 절들이 모두 필요하지는 않지만, 코드를 쓰는 사람이 알고 싶어 할 부분을 떠올리게 해 주는 좋은 점검 목록입니다.

테스트로서의 문서화 주석

문서화 주석에 예제 코드 블록을 넣으면 라이브러리를 어떻게 쓰는지 보여 주는 데 도움이 되며, 덤으로 cargo test를 실행하면 문서의 예제 코드가 테스트로 실행됩니다. 예제가 있는 문서만 한 것은 없습니다. 하지만 문서를 쓴 뒤 코드가 바뀌어 동작하지 않는 예제만큼 나쁜 것도 없습니다. 앞의 add_one 함수의 문서로 cargo test를 실행하면, 테스트 결과에 다음과 같은 부분이 나옵니다.

   Doc-tests my_crate

running 1 test
test src/lib.rs - add_one (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s

(line 5)는 예제 코드 블록이 시작하는 src/lib.rs의 다섯 번째 줄입니다. 이제 함수나 예제를 바꿔 예제의 assert_eq!가 패닉을 내게 하고 cargo test를 다시 실행하면, 문서 테스트가 예제와 코드가 서로 맞지 않는다는 것을 잡아냅니다. 코드 블록에 ignore나 no_run, compile_fail 같은 속성을 붙이면 실행 방식이 달라지므로, 모든 코드 블록이 같은 방식으로 실행되는 것은 아닙니다.

더 깊이: 문서 테스트는 어떻게 컴파일될까

rustdoc은 문서 속 예제를 그대로 컴파일하지 않고 몇 가지를 덧붙입니다. rustdoc 문서에 따르면 unused_variables나 dead_code 같은 흔한 allow 속성을 넣고, 예제에 extern crate가 없으면 extern crate my_crate;를 넣으며, fn main이 없으면 나머지 코드를 fn main() { ... }으로 감쌉니다. 그래서 예제에 main을 쓰지 않아도 되고, 예제가 문서를 읽는 사람처럼 크레이트의 공개 API만 쓰게 됩니다. # 으로 시작하는 줄은 문서에는 보이지 않지만 컴파일에는 쓰이므로, 준비 코드를 숨기는 데 씁니다.

예제를 컴파일하는 방식은 에디션에 따라 다릅니다. 에디션 가이드에 따르면 2024 에디션 전에는 코드 블록마다 실행 파일을 하나씩 만들었으므로 문서 테스트가 많으면 컴파일에 오래 걸렸습니다. 2024 에디션부터는 크레이트 전체가 2024 에디션일 때 예제들을 한 실행 파일 안의 함수들로 합쳐 컴파일하며, 실행은 여전히 테스트마다 따로 된 프로세스에서 합니다. compile_fail이나 edition 태그가 붙었거나 #![feature(...)] 같은 크레이트 속성을 쓰는 예제는 합치지 않고, 줄 번호처럼 합치면 결과가 달라지는 예제에는 standalone_crate 태그를 붙여 따로 빌드하게 합니다.

주석이 포함된 아이템

//! 형식의 문서화 주석은 주석 뒤에 오는 아이템이 아니라, 주석을 담고 있는 아이템에 문서를 답니다. 이런 문서화 주석은 흔히 크레이트 루트 파일(관례상 src/lib.rs)이나 모듈 안에서, 크레이트나 모듈 전체를 문서화하는 데 씁니다. 예를 들어 add_one 함수가 든 my_crate 크레이트의 목적을 설명하는 문서를 더하려면, src/lib.rs 파일의 맨 앞에 //!로 시작하는 문서화 주석을 둡니다.

src/lib.rs
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

/// Adds one to the number given.
// --생략--

//!로 시작하는 마지막 줄 뒤에 코드가 없다는 점을 보세요. 주석을 /// 대신 //!로 시작했으므로, 이 주석 뒤에 오는 아이템이 아니라 이 주석을 담은 아이템을 문서화합니다. 여기서 그 아이템은 크레이트 루트인 src/lib.rs 파일이므로, 이 주석은 크레이트 전체를 설명합니다. cargo doc --open을 실행하면, 이 주석이 my_crate 문서의 첫 화면에서 크레이트의 공개 아이템 목록 위에 나타납니다.

크레이트 수준 문서화 주석이 표시된 my_crate 문서 첫 화면

아이템 안의 문서화 주석은 크레이트와 모듈을 설명하는 데 특히 쓸모가 있습니다. 이 주석으로 컨테이너의 전체 목적을 설명해, 사용자가 크레이트의 구성을 이해하도록 도울 수 있습니다.

pub use로 편리한 공개 API 내보내기

크레이트를 배포할 때 공개 API의 구조는 중요하게 생각해야 할 부분입니다. 크레이트를 쓰는 사람은 여러분보다 그 구조에 덜 익숙하며, 크레이트에 모듈 계층이 깊다면 쓰고 싶은 부분을 찾기 어려울 수 있습니다.

모듈 스코프와 공개 범위에서는 pub 키워드로 아이템을 공개하는 방법과, use 키워드로 아이템을 스코프로 가져오는 방법을 다루었습니다. 그런데 크레이트를 개발하는 동안 여러분에게 자연스러운 구조가 사용자에게는 그리 편하지 않을 수 있습니다. 구조체를 여러 단계의 계층으로 정리하고 싶을 수 있지만, 계층 깊이 정의한 타입을 쓰고 싶은 사람은 그 타입이 있다는 것을 찾기 어려울 수 있습니다. 또 use my_crate::UsefulType; 대신 use my_crate::some_module::another_module::UsefulType;이라고 적어야 하는 것도 귀찮을 수 있습니다.

다행히 구조가 다른 사람이 쓰기에 편하지 않더라도, 내부 구조를 다시 짤 필요는 없습니다. 대신 pub use로 아이템을 다시 내보내(re-export), 내부 구조와 다른 공개 구조를 만들 수 있습니다. 다시 내보내기는 한곳의 공개 아이템을 다른 곳에서도 공개해, 마치 그 다른 곳에 정의된 것처럼 만듭니다. 아래 그림은 이 글의 art 예제에서 정의한 경로와 다시 내보낸 경로의 관계를 보여 줍니다.

RUST · PUBLIC API & DOCUMENTATION

pub use는 같은 타입에 사용하기 쉬운 공개 경로를 더한다

본문 art 예제에서 내부 정의와 외부 import를 연결합니다. 문서와 doctest는 그 실제 공개 이름으로 API 사용법을 설명합니다.

본문 art의 정의 경로와 다시 내보낸 공개 경로kinds의 PrimaryColor를 crate root pub use로 다시 내보내 사용자는 art PrimaryColor를 가져온다. 원래 pub mod 경로도 유지된다.art::kindspub enum PrimaryColorart 루트pub use로 재노출외부 사용자art::PrimaryColor 사용같은 아이템, 짧은 공개 경로
같은 타입의 정의와 공개 경로원 desktop과 같은 세노드두경로관계다. kinds의PrimaryColor 정의를 crate root pub use로 다시 내보내며 외부사용자는 art::PrimaryColor로 같은 타입을 가져온다. 정의 복제·새 타입 생성·private 아이템 자동공개·시간순 실행을 뜻하지 않는다. pub mod kinds가 남으면 원 art::kinds::PrimaryColor 경로도 유지된다. 문서와doctest는 실제 공개 라이브러리 이름을 사용한다.
정의 · art::kindspub enumPrimaryColor
연결 관계정의 · art::kinds · pub enum · PrimaryColor → 다시 내보내기 · 루트 · pub use · 동일한 타입
다시 내보내기 · 루트pub use동일한 타입
연결 관계다시 내보내기 · 루트 · pub use · 동일한 타입 → 외부 사용 · art::PrimaryColor · 짧은 공개 경로
외부 사용art::PrimaryColor짧은 공개 경로

정의 · art::kinds

본문의 pub enum PrimaryColor는 kinds 안에 정의됩니다.

재노출 · art 루트

pub use self::kinds::PrimaryColor로 같은 타입을 루트에 다시 내보냅니다.

사용 · 외부 크레이트

use art::PrimaryColor로 가져옵니다. pub mod kinds가 남아 있으면 art::kinds::PrimaryColor도 계속 공개됩니다.

본문 예제 13-5/13-6 · 재노출과 사용

// art/src/lib.rs
pub use self::kinds::PrimaryColor;

// 외부 크레이트
use art::PrimaryColor;

mix도 같은 방식으로 루트에 재노출됩니다. 파일을 옮기거나 타입을 복제하는 동작이 아닙니다.

본문 예제 13-1 · 문서의 실행 예제

let answer = my_crate::add_one(5);
assert_eq!(6, answer);

///는 다음 아이템, //!는 둘러싼 크레이트·모듈을 설명합니다. cargo doc은 HTML을 만들고 cargo test는 일반 Rust 문서 예제를 테스트합니다. 이 add_one 예제는 art와 별개의 크레이트입니다.

재노출만으로 원래 공개 경로가 숨겨지지는 않습니다. 문서 코드의 ignore·no_run·compile_fail 속성은 실행 방식에 영향을 주므로 모든 코드 블록이 같은 방식으로 실행되는 것은 아닙니다.

PrimaryColor는 kinds 모듈 안에 정의되어 있고, 크레이트 루트에서 pub use self::kinds::PrimaryColor;로 같은 타입을 다시 내보내면, 외부 사용자는 use art::PrimaryColor;로 그 타입을 가져올 수 있습니다. 그림에서 예제 13-5, 13-6이라고 적은 칸은 이 글에서 다시 내보내는 코드와 그것을 쓰는 코드이고, 예제 13-1은 앞의 add_one 문서 예제입니다. 다시 내보내기는 파일을 옮기거나 타입을 복제하거나 새 타입을 만드는 것이 아니며, 비공개 아이템을 저절로 공개하지도 않습니다. pub mod kinds가 남아 있으면 원래 경로인 art::kinds::PrimaryColor도 계속 공개됩니다. 문서와 문서 테스트는 이 실제 공개 이름으로 사용법을 보여 줍니다.

예를 들어 예술적 개념을 모델링하는 art라는 라이브러리를 만든다고 합시다. 이 라이브러리에는 두 모듈이 있습니다. PrimaryColor와 SecondaryColor 두 열거형이 든 kinds 모듈과, mix라는 함수가 든 utils 모듈입니다.

src/lib.rs
//! # Art
//!
//! A library for modeling artistic concepts.

pub mod kinds {
    /// The primary colors according to the RYB color model.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// The secondary colors according to the RYB color model.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    use crate::kinds::*;

    /// Combines two primary colors in equal amounts to create
    /// a secondary color.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        // --생략--
    }
}

cargo doc으로 이 크레이트의 문서를 만들면 첫 화면은 다음과 같습니다.

kinds와 utils 모듈만 목록에 나타난 art 크레이트 문서

PrimaryColor와 SecondaryColor 타입은 첫 화면에 나오지 않고, mix 함수도 나오지 않습니다. 이것들을 보려면 kinds와 utils를 눌러 들어가야 합니다. 이 라이브러리에 의존하는 다른 크레이트는, 지금 정의된 모듈 구조를 지정해 art의 아이템을 스코프로 가져오는 use 문이 필요합니다. 다음은 art 크레이트의 PrimaryColor와 mix를 쓰는 크레이트의 예입니다.

src/main.rs
use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}

art 크레이트를 쓰는 이 코드의 작성자는 PrimaryColor가 kinds 모듈에, mix가 utils 모듈에 있다는 것을 알아내야 했습니다. art 크레이트의 모듈 구조는 art를 쓰는 개발자보다 art를 만드는 개발자에게 더 의미가 있습니다. 내부 구조는 art를 어떻게 쓰는지 이해하려는 사람에게 쓸모 있는 정보를 주지 못하고, 오히려 어디를 봐야 할지 알아내고 use 문에 모듈 이름을 적어야 해서 헷갈리게 합니다.

공개 API에서 내부 구조를 없애려면, art 크레이트의 코드에 pub use 문을 더해 아이템을 최상위에 다시 내보내면 됩니다.

src/lib.rs
//! # Art
//!
//! A library for modeling artistic concepts.

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
    // --생략--
}

pub mod utils {
    // --생략--
}

이제 cargo doc이 만드는 이 크레이트의 API 문서는 첫 화면에 다시 내보낸 것들을 나열하고 링크해, PrimaryColor와 SecondaryColor 타입과 mix 함수를 더 쉽게 찾을 수 있게 해 줍니다.

다시 내보낸 색상 타입과 mix 함수가 첫 화면에 표시된 art 문서

art 크레이트의 사용자는 앞에서처럼 내부 구조를 보고 그대로 쓸 수도 있고, 다음처럼 더 편리한 구조를 쓸 수도 있습니다.

src/main.rs
use art::PrimaryColor;
use art::mix;

fn main() {
    // --생략--
}

중첩된 모듈이 많을 때 pub use로 타입을 최상위에 다시 내보내면 크레이트를 쓰는 사람의 경험이 크게 달라질 수 있습니다. pub use의 다른 흔한 쓰임은, 현재 크레이트가 의존하는 크레이트의 정의를 다시 내보내 그 정의를 현재 크레이트의 공개 API의 일부로 만드는 것입니다.

쓸모 있는 공개 API 구조를 만드는 것은 과학이라기보다 기술에 가까우며, 사용자에게 가장 잘 맞는 API를 찾기 위해 여러 번 고쳐 볼 수 있습니다. pub use를 쓰면 크레이트의 내부 구조를 유연하게 짜면서도, 그 내부 구조를 사용자에게 보여 주는 구조와 떼어 놓을 수 있습니다. 설치해 둔 크레이트의 코드를 열어 보고 내부 구조가 공개 API와 다른지 살펴보세요.

Crates.io 계정 설정하기

크레이트를 배포하려면 먼저 crates.io에 계정을 만들고 API 토큰을 받아야 합니다. crates.io 홈페이지에 들어가 GitHub 계정으로 로그인합니다. 지금은 GitHub 계정이 필요하지만, 나중에는 다른 방법으로도 계정을 만들 수 있게 될 수 있습니다. 로그인한 뒤 계정 설정에서 이메일 주소를 등록하고 인증합니다. 인증한 이메일이 없으면 crates.io가 배포를 받아 주지 않습니다. 이어서 API 토큰 페이지에서 새 토큰을 만들어 복사합니다. 이 페이지를 떠나면 토큰을 다시 볼 수 없습니다. 그다음 cargo login 명령을 실행하고, 물어보면 복사한 토큰을 붙여 넣습니다.

cargo login

이 명령은 Cargo에게 API 토큰을 알려 주고, 로컬의 ~/.cargo/credentials.toml에 저장합니다. 이 토큰은 비밀이므로 다른 누구와도 나누면 안 됩니다. 소스 코드나 로그에 넣지 말고, cargo login의 입력이나 자격 증명 공급자(credential provider)로만 다룹니다. 어떤 까닭으로든 다른 사람에게 알려졌다면, crates.io에서 그 토큰을 즉시 폐기하고 새 토큰을 만들어야 합니다.

새 크레이트에 메타데이터 추가하기

배포하고 싶은 크레이트가 있다고 합시다. 배포하기 전에, 크레이트의 Cargo.toml 파일의 [package] 섹션에 메타데이터를 더해야 합니다.

크레이트에는 고유한 이름이 필요합니다. 로컬에서 크레이트를 다루는 동안에는 이름을 마음대로 지어도 됩니다. 하지만 crates.io의 크레이트 이름은 먼저 온 사람이 먼저 차지합니다. 어떤 크레이트 이름이 한 번 쓰이면, 다른 누구도 그 이름으로 크레이트를 배포할 수 없습니다. 크레이트를 배포하기 전에 쓰고 싶은 이름을 검색해 보세요. 이미 쓰인 이름이라면 다른 이름을 찾아 Cargo.toml 파일의 [package] 섹션에 있는 name 필드를 그 새 이름으로 고쳐야 합니다.

Cargo.toml
[package]
name = "guessing_game"

고유한 이름을 골랐더라도, 이 시점에 cargo publish로 크레이트를 배포하려 하면 경고와 오류가 납니다. 아래 출력에서 warning 줄은 빠진 메타데이터의 종류를 늘어놓고, 마지막 Caused by 아래가 crates.io 서버가 업로드를 거절한 까닭입니다.

cargo publish
    Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
--생략--
error: failed to publish to registry at https://crates.io

Caused by:
  the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields

설명과 라이선스 같은 중요한 정보가 빠져서 오류가 납니다. 설명과 라이선스가 있어야 사람들이 크레이트가 무엇을 하고 어떤 조건으로 쓸 수 있는지 알 수 있습니다. Cargo.toml에 크레이트를 한두 문장으로 설명하는 description을 더합니다. 이 설명은 검색 결과에 크레이트와 함께 나옵니다. license 필드에는 라이선스 식별자 값을 적어야 합니다. 리눅스 재단(Linux Foundation)의 SPDX(Software Package Data Exchange)에 이 필드에 쓸 수 있는 식별자가 나와 있습니다. 예를 들어 크레이트에 MIT 라이선스를 적용했다면 MIT 식별자를 더합니다.

Cargo.toml
[package]
name = "guessing_game"
license = "MIT"

SPDX에 없는 라이선스를 쓰고 싶다면, 그 라이선스의 텍스트를 파일에 담아 프로젝트에 넣고, license 키 대신 license-file로 그 파일의 이름을 지정해야 합니다.

어떤 라이선스가 프로젝트에 알맞은지 안내하는 것은 이 교재의 범위를 벗어납니다. 러스트 커뮤니티의 많은 사람은 러스트와 같은 방식으로, MIT OR Apache-2.0이라는 이중 라이선스를 씁니다. 이것은 프로젝트에 여러 라이선스를 적용할 때 라이선스 식별자를 OR로 나누어 적을 수 있다는 것도 보여 줍니다.

고유한 이름, 버전, 설명, 라이선스를 더하면, 배포할 준비가 된 프로젝트의 Cargo.toml은 다음과 같을 것입니다.

Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]

Cargo 문서에는 다른 사람이 크레이트를 더 쉽게 찾고 쓰게 해 주는, 지정할 수 있는 다른 메타데이터가 설명되어 있습니다.

Crates.io에 배포하기

계정을 만들고, API 토큰을 저장하고, 크레이트의 이름을 고르고, 필요한 메타데이터를 지정했으니 배포할 준비가 되었습니다. 크레이트를 배포하면 특정 버전이 crates.io에 올라가 다른 사람이 쓸 수 있게 됩니다.

배포는 영구적이므로 조심해야 합니다. 일반적인 배포 절차에서는 같은 이름과 버전을 덮어쓸 수 없고, 올린 코드를 지울 수도 없습니다. crates.io의 주요 목표 가운데 하나는 코드의 영구적인 기록 보관소가 되어, crates.io의 크레이트에 의존하는 모든 프로젝트의 빌드가 계속 동작하게 하는 것입니다. 버전을 지울 수 있다면 그 목표를 이룰 수 없습니다. 하지만 배포할 수 있는 크레이트 버전의 수에는 제한이 없습니다.

올리기 전에, 그림의 둘째 단계처럼 무엇이 올라갈지 확인합니다.

cargo package --list
cargo publish --dry-run
cargo test

cargo package --list는 패키지에 들어갈 파일의 목록을 보여 주고, cargo publish --dry-run은 업로드하지 않고 패키징과 빌드를 해 봅니다. 패키징하면서 의존성을 해석하거나 내려받을 수도 있습니다. dry-run은 문서 테스트를 대신 실행해 주지 않으므로 cargo test도 따로 실행합니다. 이제 cargo publish 명령을 다시 실행합니다. 이번에는 성공할 것입니다.

cargo publish
    Updating crates.io index
   Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
    Packaged 6 files, 1.2KiB (895.0B compressed)
   Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
   Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
   Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
    Uploaded guessing_game v0.1.0 to registry `crates-io`
note: waiting for `guessing_game v0.1.0` to be available at registry
`crates-io`.
You may press ctrl-c to skip waiting; the crate should be available shortly.
   Published guessing_game v0.1.0 at registry `crates-io`

축하합니다! 이제 러스트 커뮤니티와 코드를 나누었고, 누구든 이 크레이트를 프로젝트의 의존성으로 쉽게 더할 수 있습니다.

이미 존재하는 크레이트의 새 버전 배포하기

크레이트를 바꾸고 새 버전을 배포할 준비가 되었다면, Cargo.toml 파일의 version 값을 바꾸고 다시 배포합니다. 바꾼 종류에 따라 다음 버전 번호를 무엇으로 할지는 유의적 버전(Semantic Versioning) 규칙을 따릅니다. 그다음 cargo publish를 실행해 새 버전을 올립니다. 그림에서처럼 수정 버전을 배포하기 전에 이전 버전을 yank해야 하는 것은 아닙니다.

cargo yank로 Crates.io에서 버전 사용하지 않게 하기

크레이트의 이전 버전을 지울 수는 없지만, 앞으로의 프로젝트가 그 버전을 새 의존성으로 더하지 않게 할 수는 있습니다. 어떤 크레이트 버전이 어떤 까닭으로 망가졌을 때 쓸모가 있습니다. 이런 상황에서 Cargo는 크레이트 버전을 yank하는 기능을 지원합니다.

버전을 yank하면, 그 버전에 의존하고 있던 기존 프로젝트는 계속 동작하게 두면서, 새 프로젝트가 그 버전에 의존하지 않게 합니다. 곧 yank는 Cargo.lock이 있는 모든 프로젝트가 망가지지 않게 하면서도, 앞으로 만들어지는 Cargo.lock 파일은 yank한 버전을 쓰지 않게 합니다. 명시적으로 cargo update --precise로 그 버전을 고르는 경우 같은 예외도 있습니다.

크레이트의 버전을 yank하려면, 이전에 배포한 크레이트의 폴더에서 cargo yank를 실행하고 yank할 버전을 지정합니다. 예를 들어 guessing_game 크레이트의 1.0.1 버전을 배포했고 이를 yank하고 싶다면, guessing_game의 프로젝트 폴더에서 다음을 실행합니다.

cargo yank --vers 1.0.1
    Updating crates.io index
        Yank [email protected]

명령에 --undo를 더하면 yank를 되돌려, 다시 그 버전에 의존하는 프로젝트를 만들 수 있게 할 수 있습니다. 버전을 지정하는 부분은 그대로 두고 --undo만 덧붙이며, 출력에 나오는 동작의 이름도 Yank에서 Unyank로 바뀝니다.

cargo yank --vers 1.0.1 --undo
    Updating crates.io index
      Unyank [email protected]

yank는 코드를 지우지 않습니다. 예를 들어 실수로 비밀 정보를 올렸다면 yank로는 지울 수 없으며, 그 비밀 정보를 곧바로 바꿔야 합니다. crates.io의 관리 정책에 따른 조치는 작성자가 실행하는 배포 명령과는 별개입니다.

정리

  • /// 문서화 주석은 다음 아이템을, //! 문서화 주석은 그것을 담은 크레이트나 모듈을 설명하며, cargo doc --open으로 HTML 문서를 만들어 볼 수 있습니다.
  • 문서화 주석의 예제 코드는 cargo test가 문서 테스트로 실행하므로, 코드가 바뀌면 맞지 않는 예제를 잡아냅니다.
  • pub use로 깊은 모듈의 아이템을 최상위에 다시 내보내면, 내부 구조는 그대로 두고 사용자에게 편한 공개 경로를 더할 수 있습니다.
  • 배포하려면 crates.io 계정의 API 토큰으로 cargo login을 하고, Cargo.toml에 고유한 name과 version, description, license를 적어야 합니다. 토큰은 비밀로 다룹니다.
  • cargo publish로 올린 버전은 덮어쓰거나 지울 수 없으므로, cargo package --list와 cargo publish --dry-run, cargo test로 먼저 확인합니다.
  • 고칠 때는 버전을 올려 다시 배포하고, 망가진 버전은 cargo yank --vers로 새 의존성이 고르지 않게 합니다. yank는 코드를 지우지 않습니다.

연습 문제

1. 어떤 경로로 쓸 수 있을까

art에 pub use self::kinds::PrimaryColor;를 더했고 pub mod kinds도 그대로 있습니다. 외부 크레이트에서 use art::PrimaryColor;와 use art::kinds::PrimaryColor;는 둘 다 되나요? 두 경로로 가져온 값은 서로 같은 타입인가요?

해설 보기

둘 다 됩니다. 다시 내보내기는 공개 경로를 하나 더할 뿐 원래 경로를 숨기지 않으므로, kinds 모듈이 공개되어 있는 한 art::kinds::PrimaryColor도 계속 쓸 수 있습니다. 두 경로는 같은 정의를 가리키므로 같은 타입이며, 한쪽으로 가져온 값을 다른 쪽 이름을 기대하는 함수에 넘길 수 있습니다.

2. 실수로 배포한 버전

1.2.0을 배포한 뒤 (가) 계산이 틀리는 버그를, (나) 코드에 API 키를 넣어 둔 것을 발견했습니다. 각각 어떻게 대응하나요?

해설 보기

(가)는 버그를 고치고 version을 1.2.1로 올려 다시 배포하며, 망가진 1.2.0을 새 프로젝트가 고르지 않게 하려면 cargo yank --vers 1.2.0을 실행합니다. (나)는 yank해도 이미 올라간 코드가 지워지지 않으므로, 먼저 그 API 키를 발급한 곳에서 키를 즉시 폐기하고 새 키로 바꿉니다. 그다음 키를 뺀 1.2.1을 배포하고, 키가 들어 있는 1.2.0을 cargo yank --vers 1.2.0으로 yank해 새 프로젝트가 고르지 않게 합니다.

다음 글 Cargo 작업공간에서는 서로 관련된 여러 패키지를 하나의 작업공간으로 묶어 함께 개발하는 방법을 살펴봅니다.