Xuất bản một Crate lên Crates.io

Chúng ta đã sử dụng các gói từ crates.io làm các phụ thuộc của dự án, nhưng bạn cũng có thể chia sẻ mã của mình với người khác bằng cách xuất bản các gói của riêng bạn. Sổ đăng ký crate tại crates.io phân phối mã nguồn của các gói của bạn, vì vậy nó chủ yếu lưu trữ mã nguồn mở.

Rust và Cargo có các tính năng giúp gói đã xuất bản của bạn dễ dàng được tìm thấy và sử dụng hơn. Chúng ta sẽ nói về một số tính năng này tiếp theo và sau đó giải thích cách xuất bản một gói.

Tạo Bình luận Tài liệu Hữu ích

Việc tài liệu hóa chính xác các gói sẽ giúp người dùng khác biết cách và khi nào sử dụng chúng, vì vậy đáng để đầu tư thời gian viết tài liệu. Trong Chương 3, chúng ta đã thảo luận về cách bình luận mã Rust bằng hai dấu gạch chéo, //. Rust cũng có một loại bình luận đặc biệt cho tài liệu, được gọi một cách thuận tiện là bình luận tài liệu, sẽ tạo ra tài liệu HTML. HTML hiển thị nội dung của các bình luận tài liệu cho các mục API công khai dành cho các lập trình viên quan tâm đến việc biết cách sử dụng crate của bạn thay vì cách crate của bạn được triển khai.

Bình luận tài liệu sử dụng ba dấu gạch chéo, ///, thay vì hai và hỗ trợ ký hiệu Markdown để định dạng văn bản. Đặt bình luận tài liệu ngay trước mục mà chúng tài liệu hóa. Listing 14-1 hiển thị bình luận tài liệu cho một hàm add_one trong một crate có tên my_crate.

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

Ở đây, chúng ta cung cấp mô tả về những gì hàm add_one làm, bắt đầu một phần với tiêu đề Examples, và sau đó cung cấp mã minh họa cách sử dụng hàm add_one. Chúng ta có thể tạo tài liệu HTML từ bình luận tài liệu này bằng cách chạy cargo doc. Lệnh này chạy công cụ rustdoc được phân phối với Rust và đặt tài liệu HTML được tạo ra trong thư mục target/doc.

Để thuận tiện, chạy cargo doc --open sẽ xây dựng HTML cho tài liệu của crate hiện tại (cũng như tài liệu cho tất cả các phụ thuộc của crate) và mở kết quả trong trình duyệt web. Điều hướng đến hàm add_one và bạn sẽ thấy cách văn bản trong bình luận tài liệu được hiển thị, như trong Hình 14-1:

Tài liệu HTML được tạo ra cho hàm `add_one` của `my_crate`

Hình 14-1: Tài liệu HTML cho hàm add_one

Các Phần Thường Được Sử dụng

Chúng ta đã sử dụng tiêu đề Markdown # Examples trong Listing 14-1 để tạo một phần trong HTML với tiêu đề "Examples." Dưới đây là một số phần khác mà tác giả crate thường sử dụng trong tài liệu của họ:

  • Panics: Các tình huống mà hàm đang được tài liệu hóa có thể gây panic. Người gọi hàm không muốn chương trình của họ bị panic nên đảm bảo rằng họ không gọi hàm trong những tình huống này.
  • Errors: Nếu hàm trả về một Result, mô tả các loại lỗi có thể xảy ra và điều kiện nào có thể gây ra những lỗi đó được trả về có thể hữu ích cho người gọi để họ có thể viết mã xử lý các loại lỗi khác nhau theo các cách khác nhau.
  • Safety: Nếu hàm là unsafe khi gọi (chúng ta thảo luận về tính không an toàn trong Chương 20), nên có một phần giải thích tại sao hàm không an toàn và đề cập đến các bất biến mà hàm mong đợi người gọi duy trì.

Hầu hết các bình luận tài liệu không cần tất cả các phần này, nhưng đây là một danh sách kiểm tra tốt để nhắc nhở bạn về các khía cạnh của mã mà người dùng sẽ quan tâm.

Bình luận Tài liệu như Các Bài Kiểm thử

Thêm các khối mã ví dụ trong bình luận tài liệu của bạn có thể giúp minh họa cách sử dụng thư viện của bạn, và làm như vậy có một lợi ích bổ sung: chạy cargo test sẽ chạy các ví dụ mã trong tài liệu của bạn như các bài kiểm thử! Không có gì tốt hơn tài liệu với các ví dụ. Nhưng không có gì tệ hơn các ví dụ không hoạt động vì mã đã thay đổi kể từ khi tài liệu được viết. Nếu chúng ta chạy cargo test với tài liệu cho hàm add_one từ Listing 14-1, chúng ta sẽ thấy một phần trong kết quả kiểm thử trông như thế này:

   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

Bây giờ, nếu chúng ta thay đổi hàm hoặc ví dụ để assert_eq! trong ví dụ gây panic và chạy cargo test một lần nữa, chúng ta sẽ thấy rằng các bài kiểm thử tài liệu phát hiện ra ví dụ và mã không còn đồng bộ với nhau!

Bình luận về Các Mục Chứa

Kiểu bình luận tài liệu //! thêm tài liệu vào mục chứa bình luận thay vì vào các mục sau bình luận. Chúng ta thường sử dụng các bình luận tài liệu này bên trong tệp gốc crate (src/lib.rs theo quy ước) hoặc bên trong một module để tài liệu hóa crate hoặc module nói chung.

Ví dụ, để thêm tài liệu mô tả mục đích của crate my_crate chứa hàm add_one, chúng ta thêm bình luận tài liệu bắt đầu bằng //! vào đầu tệp src/lib.rs, như trong Listing 14-2:

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

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

Lưu ý rằng không có bất kỳ mã nào sau dòng cuối cùng bắt đầu bằng //!. Bởi vì chúng ta bắt đầu bình luận bằng //! thay vì ///, chúng ta đang tài liệu hóa mục chứa bình luận này thay vì một mục theo sau bình luận này. Trong trường hợp này, mục đó là tệp src/lib.rs, là gốc crate. Các bình luận này mô tả toàn bộ crate.

Khi chúng ta chạy cargo doc --open, các bình luận này sẽ hiển thị trên trang đầu của tài liệu cho my_crate phía trên danh sách các mục công khai trong crate, như trong Hình 14-2.

Tài liệu HTML được tạo ra với một bình luận cho toàn bộ crate

Hình 14-2: Tài liệu được tạo ra cho my_crate, bao gồm bình luận mô tả toàn bộ crate

Bình luận tài liệu trong các mục rất hữu ích để mô tả crates và modules đặc biệt. Sử dụng chúng để giải thích mục đích tổng thể của container để giúp người dùng hiểu tổ chức của crate.

Xuất một API Công khai Thuận tiện với pub use

Cấu trúc của API công khai của bạn là một cân nhắc quan trọng khi xuất bản một crate. Những người sử dụng crate của bạn ít quen thuộc với cấu trúc hơn bạn và có thể gặp khó khăn khi tìm các phần họ muốn sử dụng nếu crate của bạn có một cấu trúc phân cấp module lớn.

Trong Chương 7, chúng ta đã đề cập đến cách làm cho các mục công khai bằng cách sử dụng từ khóa pub, và đưa các mục vào phạm vi với từ khóa use. Tuy nhiên, cấu trúc có ý nghĩa đối với bạn trong khi phát triển một crate có thể không thuận tiện cho người dùng của bạn. Bạn có thể muốn tổ chức các struct của mình trong một hệ thống phân cấp chứa nhiều cấp, nhưng sau đó những người muốn sử dụng một kiểu bạn đã định nghĩa sâu trong hệ thống phân cấp có thể gặp khó khăn khi tìm ra kiểu đó tồn tại. Họ cũng có thể bực mình khi phải nhập use my_crate::some_module::another_module::UsefulType; thay vì use my_crate::UsefulType;.

Tin tốt là nếu cấu trúc không thuận tiện cho người khác sử dụng từ một thư viện khác, bạn không cần phải sắp xếp lại tổ chức nội bộ của mình: thay vào đó, bạn có thể tái xuất các mục để tạo một cấu trúc công khai khác với cấu trúc riêng tư của bạn bằng cách sử dụng pub use. Tái xuất lấy một mục công khai ở một vị trí và làm cho nó công khai ở một vị trí khác, như thể nó được định nghĩa ở vị trí khác đó.

Ví dụ, giả sử chúng ta đã tạo một thư viện có tên art để mô hình hóa các khái niệm nghệ thuật. Trong thư viện này có hai module: module kinds chứa hai enum có tên PrimaryColorSecondaryColor và module utils chứa một hàm có tên mix, như trong Listing 14-3:

//! # 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 {
        // --snip--
        unimplemented!();
    }
}

Hình 14-3 hiển thị trang đầu tiên của tài liệu cho crate này được tạo ra bởi cargo doc sẽ trông như thế nào:

Tài liệu được tạo ra cho thư viện `art` liệt kê các module `kinds` và `utils`

Hình 14-3: Trang đầu tiên của tài liệu cho art liệt kê các module kindsutils

Lưu ý rằng các kiểu PrimaryColorSecondaryColor không được liệt kê trên trang đầu, cũng như hàm mix. Chúng ta phải nhấp vào kindsutils để xem chúng.

Một crate khác phụ thuộc vào thư viện này sẽ cần các câu lệnh use đưa các mục từ art vào phạm vi, chỉ định cấu trúc module hiện đang được định nghĩa. Listing 14-4 hiển thị một ví dụ về crate sử dụng các mục PrimaryColormix từ crate art:

use art::kinds::PrimaryColor;
use art::utils::mix;

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

Tác giả của mã trong Listing 14-4, sử dụng crate art, đã phải tìm ra rằng PrimaryColor nằm trong module kindsmix nằm trong module utils. Cấu trúc module của crate art liên quan hơn đến các nhà phát triển làm việc trên crate art hơn là những người sử dụng nó. Cấu trúc nội bộ không chứa bất kỳ thông tin hữu ích nào cho ai đó đang cố gắng hiểu cách sử dụng crate art, mà thay vào đó gây ra nhầm lẫn vì các nhà phát triển sử dụng nó phải tìm ra nơi để tìm, và phải chỉ định tên module trong các câu lệnh use.

Để loại bỏ tổ chức nội bộ khỏi API công khai, chúng ta có thể sửa đổi mã crate art trong Listing 14-3 để thêm các câu lệnh pub use để tái xuất các mục ở cấp cao nhất, như trong Listing 14-5:

//! # 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 {
    // --snip--
    /// 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 {
    // --snip--
    use crate::kinds::*;

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

Tài liệu API mà cargo doc tạo ra cho crate này bây giờ sẽ liệt kê và liên kết các tái xuất trên trang đầu, như trong Hình 14-4, làm cho các kiểu PrimaryColorSecondaryColor và hàm mix dễ tìm hơn.

Tài liệu được tạo ra cho thư viện `art` với các tái xuất trên trang đầu

Hình 14-4: Trang đầu tiên của tài liệu cho art liệt kê các tái xuất

Người dùng crate art vẫn có thể thấy và sử dụng cấu trúc nội bộ từ Listing 14-3 như được minh họa trong Listing 14-4, hoặc họ có thể sử dụng cấu trúc thuận tiện hơn trong Listing 14-5, như được hiển thị trong Listing 14-6:

use art::PrimaryColor;
use art::mix;

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

Trong các trường hợp có nhiều module lồng nhau, tái xuất các kiểu ở cấp cao nhất với pub use có thể tạo ra sự khác biệt đáng kể trong trải nghiệm của những người sử dụng crate. Một cách sử dụng phổ biến khác của pub use là tái xuất các định nghĩa của một phụ thuộc trong crate hiện tại để làm cho các định nghĩa của crate đó trở thành một phần của API công khai của crate của bạn.

Tạo một cấu trúc API công khai hữu ích là một nghệ thuật hơn là một khoa học, và bạn có thể lặp lại để tìm API hoạt động tốt nhất cho người dùng của bạn. Chọn pub use cho bạn sự linh hoạt trong cách bạn cấu trúc crate của mình nội bộ và tách biệt cấu trúc nội bộ đó khỏi những gì bạn trình bày cho người dùng của bạn. Xem xét một số mã của crates bạn đã cài đặt để xem liệu cấu trúc nội bộ của chúng có khác với API công khai của chúng hay không.

Thiết lập Tài khoản Crates.io

Trước khi bạn có thể xuất bản bất kỳ crate nào, bạn cần tạo một tài khoản trên crates.io và nhận một token API. Để làm điều này, hãy truy cập trang chủ tại crates.io và đăng nhập thông qua tài khoản GitHub. (Tài khoản GitHub hiện là một yêu cầu, nhưng trang web có thể hỗ trợ các cách khác để tạo tài khoản trong tương lai.) Sau khi đăng nhập, hãy truy cập cài đặt tài khoản của bạn tại https://crates.io/me/ và lấy khóa API của bạn. Sau đó chạy lệnh cargo login và dán khóa API của bạn khi được nhắc, như thế này:

$ cargo login
abcdefghijklmnopqrstuvwxyz012345

Lệnh này sẽ thông báo cho Cargo về token API của bạn và lưu trữ nó cục bộ trong ~/.cargo/credentials. Lưu ý rằng token này là một bí mật: không chia sẻ nó với bất kỳ ai khác. Nếu bạn chia sẻ nó với bất kỳ ai vì bất kỳ lý do gì, bạn nên thu hồi nó và tạo một token mới trên crates.io.

Thêm Metadata cho một Crate Mới

Giả sử bạn có một crate bạn muốn xuất bản. Trước khi xuất bản, bạn sẽ cần thêm một số metadata trong phần [package] của tệp Cargo.toml của crate.

Crate của bạn sẽ cần một tên duy nhất. Trong khi bạn đang làm việc trên một crate cục bộ, bạn có thể đặt tên crate bất cứ điều gì bạn thích. Tuy nhiên, tên crate trên crates.io được phân bổ theo nguyên tắc ai đến trước được phục vụ trước. Một khi tên crate được lấy, không ai khác có thể xuất bản một crate với tên đó. Trước khi cố gắng xuất bản một crate, hãy tìm kiếm tên bạn muốn sử dụng. Nếu tên đã được sử dụng, bạn sẽ cần tìm một tên khác và chỉnh sửa trường name trong tệp Cargo.toml dưới phần [package] để sử dụng tên mới cho việc xuất bản, như sau:

Tên tệp: Cargo.toml

[package]
name = "guessing_game"

Ngay cả khi bạn đã chọn một tên duy nhất, khi bạn chạy cargo publish để xuất bản crate tại thời điểm này, bạn sẽ nhận được cảnh báo và sau đó là lỗi:

$ 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.
--snip--
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

Điều này dẫn đến lỗi vì bạn thiếu một số thông tin quan trọng: một mô tả và giấy phép là bắt buộc để mọi người biết crate của bạn làm gì và theo những điều khoản nào họ có thể sử dụng nó. Trong Cargo.toml, thêm một mô tả chỉ là một câu hoặc hai, vì nó sẽ xuất hiện với crate của bạn trong kết quả tìm kiếm. Đối với trường license, bạn cần cung cấp một giá trị định danh giấy phép. Trao đổi Dữ liệu Gói Phần mềm (SPDX) của Quỹ Linux liệt kê các định danh bạn có thể sử dụng cho giá trị này. Ví dụ, để chỉ định rằng bạn đã cấp phép crate của mình bằng Giấy phép MIT, hãy thêm định danh MIT:

Tên tệp: Cargo.toml

[package]
name = "guessing_game"
license = "MIT"

Nếu bạn muốn sử dụng giấy phép không xuất hiện trong SPDX, bạn cần đặt văn bản của giấy phép đó vào một tệp, bao gồm tệp đó trong dự án của bạn, và sau đó sử dụng license-file để chỉ định tên của tệp đó thay vì sử dụng khóa license.

Hướng dẫn về giấy phép nào phù hợp cho dự án của bạn nằm ngoài phạm vi của cuốn sách này. Nhiều người trong cộng đồng Rust cấp phép cho các dự án của họ theo cách giống như Rust bằng cách sử dụng giấy phép kép là MIT OR Apache-2.0. Thực hành này cho thấy bạn cũng có thể chỉ định nhiều định danh giấy phép được phân tách bằng OR để có nhiều giấy phép cho dự án của bạn.

Với một tên duy nhất, phiên bản, mô tả của bạn, và giấy phép được thêm vào, tệp Cargo.toml cho một dự án sẵn sàng xuất bản có thể trông như thế này:

Tên tệp: Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"
description = "Một trò chơi vui nhộn nơi bạn đoán số mà máy tính đã chọn."
license = "MIT OR Apache-2.0"

[dependencies]

Tài liệu của Cargo mô tả các metadata khác bạn có thể chỉ định để đảm bảo người khác có thể khám phá và sử dụng crate của bạn dễ dàng hơn.

Xuất bản lên Crates.io

Bây giờ bạn đã tạo một tài khoản, lưu token API của bạn, chọn một tên cho crate của bạn, và chỉ định các metadata bắt buộc, bạn đã sẵn sàng để xuất bản! Xuất bản một crate tải lên một phiên bản cụ thể lên crates.io để người khác sử dụng.

Hãy cẩn thận, vì một lần xuất bản là vĩnh viễn. Phiên bản không bao giờ có thể được ghi đè, và mã không thể bị xóa. Một mục tiêu chính của crates.io là hoạt động như một kho lưu trữ vĩnh viễn của mã để các bản dựng của tất cả các dự án phụ thuộc vào crates từ crates.io sẽ tiếp tục hoạt động. Cho phép xóa phiên bản sẽ làm cho việc đạt được mục tiêu đó là không thể. Tuy nhiên, không có giới hạn về số lượng phiên bản crate bạn có thể xuất bản.

Chạy lệnh cargo publish một lần nữa. Lần này nó sẽ thành công:

$ cargo publish
    Updating crates.io index
   Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
   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)

Chúc mừng! Bây giờ bạn đã chia sẻ mã của mình với cộng đồng Rust, và bất kỳ ai cũng có thể dễ dàng thêm crate của bạn làm phụ thuộc của dự án của họ.

Xuất bản Một Phiên bản Mới của Crate Hiện có

Khi bạn đã thực hiện các thay đổi đối với crate của mình và sẵn sàng phát hành một phiên bản mới, bạn thay đổi giá trị version được chỉ định trong tệp Cargo.toml của bạn và xuất bản lại. Sử dụng các quy tắc Semantic Versioning để quyết định một số phiên bản tiếp theo phù hợp dựa trên các loại thay đổi bạn đã thực hiện. Sau đó chạy cargo publish để tải lên phiên bản mới.

Không dùng nữa Các Phiên bản từ Crates.io với cargo yank

Mặc dù bạn không thể xóa các phiên bản trước đó của một crate, bạn có thể ngăn bất kỳ dự án tương lai nào thêm chúng làm phụ thuộc mới. Điều này hữu ích khi một phiên bản crate bị lỗi vì lý do nào đó. Trong những tình huống như vậy, Cargo hỗ trợ yanking (rút lại) một phiên bản crate.

Yanking một phiên bản ngăn các dự án mới phụ thuộc vào phiên bản đó trong khi cho phép tất cả các dự án hiện có phụ thuộc vào nó tiếp tục. Về cơ bản, rút lại có nghĩa là tất cả các dự án với Cargo.lock sẽ không bị hỏng, và bất kỳ tệp Cargo.lock nào được tạo ra trong tương lai sẽ không sử dụng phiên bản đã bị rút lại.

Để rút lại một phiên bản của crate, trong thư mục của crate mà bạn đã xuất bản trước đó, chạy cargo yank và chỉ định phiên bản bạn muốn rút lại. Ví dụ, nếu chúng ta đã xuất bản một crate có tên guessing_game phiên bản 1.0.1 và chúng ta muốn rút lại nó, trong thư mục dự án cho guessing_game chúng ta sẽ chạy:

$ cargo yank --vers 1.0.1
    Updating crates.io index
        Yank guessing_game@1.0.1

Bằng cách thêm --undo vào lệnh, bạn cũng có thể hoàn tác việc rút lại và cho phép các dự án bắt đầu phụ thuộc vào một phiên bản một lần nữa:

$ cargo yank --vers 1.0.1 --undo
    Updating crates.io index
      Unyank guessing_game@1.0.1

Việc rút lại không xóa bất kỳ mã nào. Ví dụ, nó không thể xóa các bí mật vô tình được tải lên. Nếu điều đó xảy ra, bạn phải đặt lại các bí mật đó ngay lập tức.