Mempublikasikan Crate ke Crates.io
Kita sudah memakai packages dari crates.io sebagai dependencies buat project kita, tapi kita juga bisa nge-share kode kita sama orang lain dengan mempublikasikan packages milik kita sendiri. Registry crate di crates.io mendistribusikan source code dari packages kita, jadi ia pada dasarnya meng-host kode yang open source (sumber terbuka).
Rust dan Cargo punya berbagai fitur yang bikin package yang kita publikasikan jadi lebih gampang dicari dan dipakai orang. Kita bakal membahas beberapa fitur ini lalu menjelaskan gimana caranya mempublikasikan sebuah package.
Menulis Komentar Dokumentasi yang Berguna
Mendokumentasikan packages kita secara akurat bakal membantu para user
lain tahu gimana dan kapan mereka bisa memakainya, jadi sangat sepadan
menghabiskan waktu buat nulis dokumentasi. Di Bab 3, kita sudah membahas gimana
cara memberi komentar di kode Rust menggunakan dua garis miring, //. Rust
juga punya jenis komentar khusus buat dokumentasi, yang lebih enak disebut
documentation comment (komentar dokumentasi), yang bakal men-generate
dokumentasi dalam format HTML. HTML ini menampilkan isi dari komentar dokumentasi
buat item-item API public yang ditujukan buat para programmer yang tertarik
buat tahu gimana cara memakai crate kita, bukannya gimana crate kita itu
diimplementasikan.
Komentar dokumentasi memakai tiga garis miring, ///, bukannya dua dan
mendukung notasi Markdown buat memformat teksnya. Taruh komentar dokumentasi
persis sebelum item yang lagi mereka dokumentasikan. Listing 14-1 menunjukkan
komentar dokumentasi buat sebuah fungsi add_one di dalam sebuah crate
bernama 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
}
Di sini, kita memberikan deskripsi tentang apa yang dilakukan sama fungsi add_one,
memulai sebuah bagian (section) dengan judul Examples (Contoh), dan kemudian
menyediakan kode yang mendemonstrasikan gimana cara memakai fungsi add_one. Kita
bisa men-generate dokumentasi HTML dari komentar dokumentasi ini dengan
menjalankan cargo doc. Perintah ini menjalankan tool rustdoc yang
didistribusikan bersama Rust dan menaruh dokumentasi HTML hasilnya ke dalam
direktori target/doc.
Biar lebih praktis, menjalankan cargo doc --open bakal mem-build HTML buat
dokumentasi dari crate kita saat ini (beserta dokumentasi buat semua
dependencies crate kita) lalu membuka hasilnya di web browser. Arahkan
navigasi ke fungsi add_one dan kita bakal melihat gimana teks di komentar
dokumentasi tersebut dirender, seperti yang ditunjukkan di Gambar 14-1.
Gambar 14-1: Dokumentasi HTML untuk fungsi add_one
Bagian-bagian yang Sering Dipakai
Kita memakai heading Markdown # Examples di Listing 14-1 buat membikin
sebuah bagian di HTML-nya dengan judul “Examples.” Berikut ini beberapa bagian
lain yang sering sekali dipakai oleh para pembuat crate di dokumentasi mereka:
- Panics: Skenario-skenario di mana fungsi yang didokumentasikan ini bisa mengalami panic. Kode pemanggil fungsi ini yang tidak mau programnya mengalami panic harus memastikan kalau mereka tidak memanggil fungsi ini di situasi-situasi tersebut.
- Errors: Kalau fungsinya mengembalikan sebuah
Result, mendeskripsikan jenis-jenis error apa aja yang mungkin terjadi dan kondisi apa yang mungkin menyebabkan error-error itu dikembalikan bisa sangat ngebantu pemanggil agar mereka bisa nulis kode buat menangani berbagai jenis error dengan cara yang berbeda-beda. - Safety: Kalau fungsinya itu
unsafe(tidak aman) buat dipanggil (kita membahas soal unsafety di Bab 20), harusnya ada bagian yang menjelaskan kenapa fungsi itu tidak aman dan mencakup invariants (batasan) apa yang diharapkan oleh fungsi tersebut buat dipatuhi sama pemanggilnya.
Kebanyakan komentar dokumentasi tidak perlu punya semua bagian ini, tapi ini adalah checklist yang bagus buat ngingetin kita soal aspek-aspek kode kita yang bakal bikin user tertarik buat tahu.
Komentar Dokumentasi Sebagai Tests (Pengujian)
Menambahkan blok-blok contoh kode di dalam komentar dokumentasi kita bisa
membantu mendemonstrasikan gimana cara memakai library kita, dan
melakukannya punya keuntungan ekstra: menjalankan cargo test bakal
menjalankan contoh kode di dokumentasi kita tersebut sebagai pengujian!
Tidak ada yang lebih baik dari dokumentasi yang disertai contoh. Tapi tidak
ada yang lebih parah dari contoh yang tidak jalan gara-gara kodenya udah
diubah sejak dokumentasinya ditulis. Kalau kita menjalankan cargo test
dengan dokumentasi buat fungsi add_one dari Listing 14-1, kita bakal melihat
sebuah bagian di hasil pengujian yang kelihatan kayak gini:
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
Sekarang, kalau kita mengubah fungsinya atau contohnya sehingga assert_eq!
di contoh tersebut mengalami panic, lalu menjalankan cargo test lagi, kita
bakal melihat kalau doc tests bakal menangkap bahwa contoh dan kodenya
sudah tidak sinkron lagi!
Mengomentari Item Penampungnya (Contained Items)
Gaya komentar dokumentasi //! menambahkan dokumentasi ke item yang menampung
komentar tersebut, bukannya ke item-item yang mengikuti komentarnya. Kita
biasanya memakai jenis komentar dokumentasi ini di dalam file crate root
(src/lib.rs secara konvensi) atau di dalam sebuah modul buat mendokumentasikan
crate atau modulnya secara keseluruhan.
Misalnya, buat menambahkan dokumentasi yang mendeskripsikan tujuan dari
crate my_crate yang mengandung fungsi add_one, kita bisa menambahkan
komentar dokumentasi yang dimulai dengan //! ke bagian paling awal dari
file src/lib.rs, seperti yang ditunjukkan di 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
}
my_crate secara keseluruhanPerhatikan bahwa tidak ada kode apa pun setelah baris terakhir yang dimulai
dengan //!. Karena kita memulai komentarnya dengan //! bukannya ///,
kita mendokumentasikan item yang menampung komentar ini, bukannya item yang
ada setelah komentar ini. Di kasus ini, item tersebut adalah file
src/lib.rs, yang mana itu adalah crate root. Komentar-komentar ini
mendeskripsikan crate-nya secara keseluruhan.
Pas kita menjalankan cargo doc --open, komentar-komentar ini bakal tampil
di halaman depan dokumentasi untuk my_crate tepat di atas daftar item-item
public di crate tersebut, seperti yang ditunjukkan di Gambar 14-2.
Gambar 14-2: Dokumentasi yang dirender buat my_crate, termasuk komentar yang mendeskripsikan crate secara keseluruhan
Komentar dokumentasi yang ditaruh di dalam item sangat berguna buat mendeskripsikan crates dan juga modules (modul). Pakailah mereka buat menjelaskan tujuan keseluruhan dari wadah (container)-nya demi membantu para user kita memahami organisasi crate tersebut.
Mengekspor API Public yang Nyaman dengan pub use
Struktur dari API public kita adalah pertimbangan besar saat mempublikasikan sebuah crate. Orang-orang yang memakai crate kita itu kurang familier dengan strukturnya dibandingkan kita dan mungkin bakal kesusahan mencari bagian-bagian yang mau mereka pakai kalau crate kita punya hierarki modul yang besar.
Di Bab 7, kita sudah membahas gimana cara membikin item jadi public memakai
keyword pub, dan gimana cara membawa item ke dalam scope memakai keyword
use. Namun, struktur yang menurut kita masuk akal saat kita lagi ngembangin
sebuah crate mungkin aja tidak terlalu nyaman buat para user kita. Kita
mungkin mau mengatur structs kita di dalam sebuah hierarki yang terdiri dari
beberapa tingkat, tapi nanti orang yang mau memakai tipe yang sudah kita
definisikan jauh di kedalaman hierarki itu mungkin bakal kesulitan buat tahu kalau
tipe tersebut ada. Mereka juga mungkin bakal jengkel karena harus mengetikkan
use my_crate::some_module::another_module::UsefulType; bukannya sekadar
use my_crate::UsefulType;.
Kabar baiknya adalah kalau struktur yang kita buat tidak nyaman buat orang lain
pakai dari library mereka, kita tidak harus menata ulang organisasi
internalnya: sebagai gantinya, kita bisa me-re-export (mengekspor ulang)
item-item buat bikin sebuah struktur public yang beda dari struktur private
kita dengan menggunakan pub use. Re-exporting mengambil sebuah item public
di satu lokasi lalu membikinnya jadi public di lokasi lain, seolah-olah
item tersebut didefinisikan di lokasi yang lain itu.
Misalnya, katakanlah kita bikin sebuah library bernama art untuk memodelkan
konsep-konsep kesenian. Di dalam library ini ada dua modul: modul kinds yang
berisi dua enum bernama PrimaryColor dan SecondaryColor, serta modul
utils yang berisi fungsi bernama mix, seperti yang ditunjukkan di 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!();
}
}
art yang punya item-item yang diatur ke dalam modul kinds dan utilsGambar 14-3 menunjukkan seperti apa jadinya halaman depan dokumentasi untuk
crate ini yang di-generate oleh cargo doc.
Gambar 14-3: Halaman depan dokumentasi untuk art yang mendaftarkan modul kinds dan utils
Perhatikan bahwa tipe PrimaryColor dan SecondaryColor tidak terdaftar di
halaman depan, fungsi mix juga tidak ada. Kita harus mengklik kinds dan
utils buat melihat mereka.
Crate lain yang bergantung pada library ini bakal butuh statements
use yang membawa item-item dari art ke dalam scope, dengan menentukan
struktur modul yang saat ini didefinisikan. Listing 14-4 menunjukkan contoh
sebuah crate yang memakai item PrimaryColor dan mix dari crate art.
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
art dengan struktur internalnya yang tereksporPembuat kode di Listing 14-4, yang memakai crate art, harus mencari tahu
kalau PrimaryColor ada di dalam modul kinds dan mix ada di dalam
modul utils. Struktur modul dari crate art ini lebih relevan buat para
developer yang mengerjakan crate art tersebut dibanding buat orang yang
memakainya. Struktur internalnya tidak memberikan informasi yang berguna buat
seseorang yang lagi mencoba buat paham gimana cara memakai crate art
tersebut, melainkan malah bikin bingung karena developer yang memakainya harus
nyari tahu di mana harus mencari, dan harus mengetikkan nama-nama modulnya
di statement use.
Buat menghilangkan organisasi internalnya dari API public, kita bisa
memodifikasi kode crate art di Listing 14-3 dengan menambahkan statements
pub use buat me-re-export item-item itu di tingkat paling atas, seperti
yang ditunjukkan di 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
}
}
pub use buat me-re-export itemDokumentasi API yang di-generate sama cargo doc buat crate ini sekarang
bakal mendaftarkan dan menaruh tautan ke re-exports tersebut di halaman depan,
seperti yang ditunjukkan di Gambar 14-4, sehingga bikin tipe PrimaryColor dan
SecondaryColor serta fungsi mix jadi lebih gampang dicari.
Gambar 14-4: Halaman depan dokumentasi buat art yang mendaftarkan hasil re-exports
Para pengguna crate art masih bisa melihat dan memakai struktur internalnya
dari Listing 14-3 seperti yang didemonstrasikan di Listing 14-4, atau mereka
bisa memakai struktur yang lebih nyaman dari Listing 14-5, seperti yang
ditunjukkan di Listing 14-6.
use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
artDi kasus di mana ada banyak modul yang bersarang (nested modules), me-re-export
tipe di level teratas dengan pub use bisa bikin perbedaan yang sangat besar
dalam pengalaman (experience) orang-orang yang memakai crate tersebut.
Kegunaan umum lain dari pub use adalah buat me-re-export definisi dari
sebuah dependency (dependensi) di crate saat ini buat bikin definisi
crate itu jadi bagian dari API public crate kita.
Membikin struktur API public yang berguna itu lebih ke arah seni ketimbang sains
eksak, dan kita bisa melakukan iterasi buat nemuin API apa yang bekerja paling
baik buat para user kita. Memilih buat pakai pub use ngasih kita fleksibilitas
dalam mengatur gimana struktur internal crate kita dan melepaskan kaitan
(decouples) antara struktur internal itu dari apa yang kita tampilkan ke para user
kita. Coba deh lihat beberapa kode dari crates yang udah kita instal buat melihat
apakah struktur internal mereka berbeda dari API public mereka.
Menyiapkan Akun Crates.io
Sebelum kita bisa mempublikasikan crates apa pun, kita harus bikin akun dulu
di crates.io dan dapetin API token. Buat melakukannya,
kunjungi halaman depan di crates.io lalu login pakai akun
GitHub. (Akun GitHub saat ini adalah syarat wajibnya, tapi situsnya mungkin bakal
mendukung cara lain buat bikin akun di masa depan.) Setelah kita login, kunjungi
pengaturan akun kita di https://crates.io/me/ lalu ambil
kunci (key) API kita. Kemudian jalankan perintah cargo login dan paste
kunci API kita pas diminta, kayak gini:
$ cargo login
abcdefghijklmnopqrstuvwxyz012345
Perintah ini bakal ngasih tahu Cargo soal token API kita lalu menyimpannya secara lokal di ~/.cargo/credentials.toml. Perhatikan bahwa token ini adalah sebuah rahasia (secret): jangan bagikan ke orang lain. Kalau kita membagikannya ke orang lain dengan alasan apa pun, kita harus menariknya (revoke) dan membuat token baru di crates.io.
Menambahkan Metadata ke Crate Baru
Katakanlah kita punya sebuah crate yang mau kita publikasikan. Sebelum dipublish,
kita harus menambahkan beberapa metadata di bagian [package] dari file Cargo.toml
milik crate tersebut.
Crate kita bakal butuh nama yang unik. Selama kita mengerjakan crate secara
lokal, kita bisa menamai crate sesuka kita. Namun, nama crate di
crates.io itu dialokasikan berdasarkan siapa cepat dia
dapat (first-come, first-served). Begitu sebuah nama crate sudah diambil,
tidak ada orang lain yang bisa mempublikasikan crate dengan nama tersebut.
Sebelum mencoba buat mempublikasikan sebuah crate, carilah nama yang pengen kita
pakai. Kalau nama tersebut sudah terpakai, kita harus mencari nama lain lalu
mengedit field name di dalam file Cargo.toml di bawah bagian [package] buat
memakai nama baru tersebut untuk publikasi, kayak gini:
Nama file: Cargo.toml
[package]
name = "guessing_game"
Meskipun kita sudah milih nama yang unik, saat kita menjalankan cargo publish
buat mempublikasikan crate di titik ini, kita bakal dapat peringatan dan lalu
sebuah error:
$ 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
Ini menghasilkan error karena kita kelupaan beberapa informasi yang krusial:
sebuah deskripsi (description) dan lisensi (license) diwajibkan supaya
orang-orang bisa tahu apa yang dilakukan sama crate kita dan di bawah
ketentuan (terms) apa mereka bisa memakainya. Di Cargo.toml, tambahkan sebuah
deskripsi yang hanya terdiri dari satu atau dua kalimat aja, karena itu bakal
muncul bareng crate kita di hasil pencarian. Buat field license, kita harus
memberikan nilai pengenal lisensi (license identifier value).
Software Package Data Exchange (SPDX) milik Linux Foundation
mendaftarkan pengenal yang bisa kita pakai buat nilai ini. Misalnya, buat
menentukan kalau kita melisensikan crate kita memakai Lisensi MIT, tambahkan
pengenal MIT:
Nama file: Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
Kalau kita mau memakai lisensi yang tidak muncul di SPDX, kita perlu menaruh teks
dari lisensi tersebut di sebuah file, memasukkan file itu di project kita, lalu
memakai license-file buat menentukan nama dari file tersebut sebagai ganti dari
memakai key license.
Panduan soal lisensi mana yang pas buat project kita ada di luar cakupan buku ini.
Banyak orang di komunitas Rust melisensikan project mereka pakai cara yang sama
kayak Rust dengan memakai dual license (lisensi ganda) MIT OR Apache-2.0.
Praktik ini menunjukkan kalau kita juga bisa menentukan lebih dari satu
pengenal lisensi yang dipisahkan oleh OR buat punya banyak lisensi di
project kita.
Dengan nama yang unik, versi, deskripsi kita, dan lisensi yang sudah ditambahkan, file Cargo.toml untuk project yang siap dipublish mungkin bakal kelihatan kayak gini:
Nama file: 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]
Dokumentasi Cargo mendeskripsikan metadata lain yang bisa kita tentukan buat memastikan orang lain bisa lebih gampang menemukan dan memakai crate kita.
Mempublikasikan ke Crates.io
Sekarang karena kita udah bikin akun, nyimpan token API kita, memilih nama buat crate kita, dan menentukan metadata yang dibutuhin, kita sudah siap buat melakukan publikasi! Mempublikasikan sebuah crate mengunggah versi spesifiknya ke crates.io biar orang lain bisa pakai.
Hati-hati ya, karena mempublikasikan itu sifatnya permanen. Versi itu tidak akan pernah bisa ditimpa (overwritten), dan kodenya tidak bisa dihapus kecuali di situasi-situasi tertentu. Salah satu tujuan utama dari Crates.io adalah bertindak sebagai arsip kode yang permanen sehingga proses build dari semua project yang bergantung pada crates dari crates.io bakal terus berfungsi. Mengizinkan penghapusan versi bakal bikin pemenuhan tujuan tersebut jadi mustahil. Namun, tidak ada batas buat seberapa banyak versi crate yang bisa kita publikasikan.
Jalankan perintah cargo publish lagi. Kali ini harusnya udah berhasil:
$ 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`
Selamat! Kita sekarang sudah nge-share kode kita sama komunitas Rust, dan siapa pun bisa dengan gampang nambahin crate kita sebagai dependency di project mereka.
Mempublikasikan Versi Baru dari Crate yang Sudah Ada
Saat kita bikin perubahan di crate kita dan udah siap buat ngerilis versi
baru, kita cukup ganti nilai version yang ditentukan di file Cargo.toml
kita dan publish ulang (republish). Pakai aturan Semantic Versioning
buat nentuin apa nomor versi selanjutnya yang paling pas, berdasarkan
jenis perubahan yang sudah kita buat. Terus jalankan cargo publish buat
mengunggah versi barunya.
Melarang Penggunaan Versi Lama dari Crates.io dengan cargo yank
Meskipun kita tidak bisa menghapus versi lama dari sebuah crate, kita bisa mencegah project-project di masa depan buat menambahkan versi tersebut sebagai dependency baru. Hal ini berguna pas sebuah versi crate ternyata rusak (broken) karena suatu alasan tertentu. Di situasi semacam itu, Cargo mendukung aksi “menyentak” (yanking) sebuah versi crate.
Yanking sebuah versi mencegah project baru untuk bisa bergantung pada versi tersebut sekaligus tetap membiarkan semua project yang sudah ada yang bergantung pada versi itu buat terus berjalan. Pada dasarnya, aksi yank berarti bahwa semua project yang sudah punya file Cargo.lock tidak akan rusak, dan file Cargo.lock apa pun yang di-generate di masa depan tidak bakal memakai versi yang di-yank tersebut.
Buat menge-yank sebuah versi crate, dari dalam direktori crate yang
tadinya sudah kita publikasikan, jalankan cargo yank dan tentukan versi mana
yang mau kita yank. Misalnya, kalau kita sudah mempublikasikan sebuah crate
bernama guessing_game versi 1.0.1 dan kita mau menge-yank-nya, di dalam
direktori project buat guessing_game kita bakal menjalankan:
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1
Dengan menambahkan --undo ke dalam command-nya, kita juga bisa
membatalkan aksi yank (meng-unyank) lalu mengizinkan project-project buat
mulai bergantung lagi pada sebuah versi:
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1
Aksi yank sama sekali tidak menghapus kode apa pun. Ia tidak bisa, misalnya, menghapus data rahasia (secrets) yang tidak sengaja terunggah. Kalau hal seperti itu terjadi, kita harus langsung nge-reset rahasia tersebut.