Lewati ke konten

Cargo dan Crate di Rust

Di halaman Mulai dengan Rust, kamu sudah memakai cargo new dan cargo run. Itu baru permulaan. Cargo adalah build tool Rust sekaligus package manager-nya. Cargo meng-compile kodemu, menjalankan test, merapikan format file, dan mengunduh library yang ditulis orang lain.

Library-library itu disebut crate (paket kode Rust). Bayangkan crate seperti aplikasi di app store: orang lain sudah memecahkan sebuah masalah (membaca JSON, membuat web request, mem-parse tanggal), dan kamu bisa menambahkan solusi mereka ke proyekmu dengan satu perintah. Halaman ini menunjukkan cara kerjanya, dan cara memilih crate yang bagus.

Ada dua istilah yang akan sering kamu dengar:

  • Crate adalah satu unit kode Rust yang di-build oleh compiler. Crate bisa berupa binary (program yang bisa dijalankan, dimulai dari src/main.rs) atau library (kode untuk dipakai program lain, dimulai dari src/lib.rs).
  • Package adalah apa yang dibuat oleh cargo new: sebuah folder berisi file Cargo.toml dan satu atau lebih crate di dalamnya.

Dalam percakapan sehari-hari, orang menyebut keduanya “crate”. Kalau seseorang bilang “pakai crate serde”, maksudnya “tambahkan package serde dari crates.io ke proyekmu”.

Setiap package punya file Cargo.toml. File ini ditulis dalam format TOML (bahasa Inggris), format pengaturan yang sederhana. Inilah yang kamu dapat dari cargo new hello_rust:

[package]
name = "hello_rust"
version = "0.1.0"
edition = "2024"
[dependencies]
  • [package] menjelaskan proyekmu. name dan version adalah yang akan dilihat orang lain kalau kamu memublikasikannya.
  • edition memilih edisi bahasa Rust yang dipakai. Edisi (2018, 2021, 2024) memungkinkan Rust berkembang tanpa merusak kode lama. Biarkan saja apa pun yang ditulis oleh cargo new.
  • [dependencies] berisi daftar crate yang dipakai kodemu. Awalnya kosong.

Seiring proyek berkembang, kamu mungkin melihat bagian-bagian lain:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dev-dependencies]
# hanya dipakai oleh test dan contoh, bukan oleh program aslimu
pretty_assertions = "1.4"
[profile.release]
# pengaturan untuk `cargo build --release`
opt-level = 3

Kamu bisa mengedit [dependencies] secara manual, tapi cara termudah adalah cargo add. Perintah ini mencari versi terbaru di crates.io dan menuliskan barisnya untukmu:

Terminal window
cargo add serde_json

Sebagian crate punya bagian opsional yang disebut features. Secara default, fitur-fitur ini dimatikan supaya hasil build tetap kecil. Kamu menyalakannya dengan --features:

Terminal window
cargo add serde --features derive

Setelah dua perintah ini, Cargo.toml kamu akan berisi kira-kira seperti ini:

[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"

Sekarang pakai crate itu di kodemu. cargo run berikutnya akan mengunduhnya, meng-compile-nya sekali, lalu menautkannya ke programmu:

use serde_json::json;
fn main() {
let crab = json!({ "legs": 10, "name": "Ferris" });
println!("{}", crab);
println!("Nama: {}", crab["name"]);
}

Output:

{"legs":10,"name":"Ferris"}
Nama: "Ferris"
▶ Coba sendiri

Tanda kutip di sekitar "Ferris" muncul karena crab["name"] masih berupa nilai JSON, bukan string Rust biasa.

Rust Playground sudah menyertakan crate-crate paling populer, jadi kode ini bisa jalan di sana tanpa cargo add. Di komputermu sendiri, kamu perlu menjalankan cargo add serde_json terlebih dahulu. Kamu akan belajar jauh lebih banyak tentang crate ini di halaman JSON dengan Serde.

Untuk menghapus crate yang tidak kamu butuhkan lagi, jalankan cargo remove serde_json.

Saat kamu menulis serde_json = "1.0", kamu tidak meminta tepat versi 1.0. Maksudmu adalah “versi apa pun yang kompatibel dengan 1.0”. Cargo mengikuti semantic versioning (SemVer): versi ditulis MAJOR.MINOR.PATCH, dan hanya perubahan pada angka major yang boleh merusak kodemu.

Kamu menulis Cargo boleh memakai Alasannya
"1.0" (sama dengan "^1.0") >=1.0.0 dan <2.0.0 semua versi 1.x seharusnya kompatibel
"1.4.2" >=1.4.2 dan <2.0.0 minimal 1.4.2, tapi tetap 1.x
"0.4" >=0.4.0 dan <0.5.0 sebelum 1.0, angka minor dianggap bisa merusak
"~1.4" >=1.4.0 dan <1.5.0 hanya update patch
"=1.4.2" tepat 1.4.2 jarang dibutuhkan

Lalu siapa yang menentukan versi yang persis? Itulah tugas Cargo.lock. Saat pertama kali build, Cargo memilih versi terbaru yang cocok dengan aturanmu dan mencatatnya di Cargo.lock. Setiap build setelahnya memakai versi yang persis sama, di komputermu maupun di komputer orang lain.

  • Jangan edit Cargo.lock secara manual. Cargo yang mengurusnya.
  • Commit file ini ke git, terutama untuk program. Dengan begitu, rekan timmu dan CI kamu mem-build hal yang persis sama dengan yang kamu build.
  • cargo update berpindah ke versi terbaru yang masih cocok dengan Cargo.toml, lalu menulis ulang Cargo.lock. Perintah ini tidak pernah melompat ke versi major baru; untuk itu, kamu harus mengubah Cargo.toml sendiri.

Ada tiga situs yang akan kamu pakai berulang kali (semuanya berbahasa Inggris):

  • crates.io adalah registry resmi. Semua crate publik ada di sini. Cari dengan kata seperti “csv” atau “date”.
  • docs.rs menampung dokumentasi setiap crate, yang dibuat secara otomatis. Contohnya, docs.rs/serde_json.
  • lib.rs adalah indeks tidak resmi yang lebih cepat dan mengelompokkan crate berdasarkan kategori. Sangat cocok untuk melihat-lihat.

Banyak crate memecahkan masalah yang sama, jadi bagaimana cara memilihnya? Sebelum menambahkan sebuah crate, luangkan waktu sebentar untuk mengecek:

  1. Jumlah unduhan. Crate yang populer punya jutaan unduhan. Artinya, sudah banyak orang yang menemukan dan memperbaiki bug-nya sebelum kamu.
  2. Update terbaru. Lihat tanggal rilis terakhir. Crate yang tidak berubah selama bertahun-tahun mungkin masih baik-baik saja (ada yang memang sudah “selesai”), tapi pastikan repository-nya tidak penuh issue yang tidak dijawab.
  3. Dokumentasi. Buka halaman docs.rs-nya. Crate yang bagus punya halaman depan yang jelas dengan contoh yang bisa kamu salin.
  4. Versi 1.0 atau lebih. Tidak wajib, tapi ini menandakan penulisnya menjanjikan API yang stabil.
  5. Sedikit dependency. Setiap crate yang kamu tambahkan membawa dependency-nya sendiri. Makin kecil, makin mudah dipercaya.

Kalau ragu, cari “rust” ditambah masalahmu, lalu lihat crate mana yang terus-menerus direkomendasikan oleh komunitas.

Kamu tidak perlu menghafal semuanya. Simpan tabel ini di dekatmu, dan kamu akan hafal sendiri dengan sering memakainya:

Perintah Fungsinya
cargo new my_app Membuat proyek binary baru (tambahkan --lib untuk library)
cargo build Meng-compile ke target/debug/
cargo run Meng-compile lalu menjalankan (argumen ditulis setelah --: cargo run -- input.txt)
cargo check Mengecek error tanpa membuat program. Jauh lebih cepat daripada build
cargo test Menjalankan test (lihat Testing di Rust)
cargo fmt Merapikan format semua kodemu dengan gaya standar
cargo clippy Menjalankan linter. Clippy menyarankan kode yang lebih sederhana dan lebih idiomatis
cargo doc --open Membuat dokumentasi HTML untuk proyekmu dan semua dependency-nya, lalu membukanya
cargo update Memperbarui Cargo.lock ke versi kompatibel terbaru
cargo build --release Meng-compile program yang sudah dioptimalkan ke target/release/

Kebiasaan yang baik: sering jalankan cargo check selagi menulis kode, lalu cargo fmt dan cargo clippy sebelum commit. cargo doc --open juga permata tersembunyi. Perintah ini memberimu dokumentasi untuk versi crate yang persis kamu pakai, dan bisa dipakai offline.

Secara default, Cargo membuat build debug. Build ini cepat di-compile dan menyimpan pemeriksaan keamanan tambahan, misalnya panic saat integer overflow. Itu sempurna selama kamu masih mengembangkan program.

Build release (cargo build --release atau cargo run --release) lebih lama di-compile, tapi berjalan jauh lebih cepat, sering kali 10 kali lipat atau lebih. Pakai build ini saat kamu merilis programmu atau mengukur kecepatannya.

Kodemu bahkan bisa tahu jenis build yang sedang dipakai:

fn main() {
if cfg!(debug_assertions) {
println!("Build debug: pemeriksaan tambahan aktif");
} else {
println!("Build release: dioptimalkan untuk kecepatan");
}
}

Output:

Build debug: pemeriksaan tambahan aktif
▶ Coba sendiri

Di Playground, kamu bisa mengganti “Debug” menjadi “Release” lewat menu, lalu menjalankannya lagi untuk melihat baris yang lain.

Sebuah package bisa berisi library dan binary sekaligus. Ini susunan yang sangat umum. Logika utamamu disimpan di src/lib.rs, sementara src/main.rs tetap kecil dan hanya memanggilnya:

greeter/
├── Cargo.toml
└── src/
├── lib.rs # crate library: logika yang bisa dipakai ulang
└── main.rs # crate binary: program yang kamu jalankan
src/lib.rs
pub fn greet(name: &str) -> String {
format!("Halo, {name}!")
}
src/main.rs
fn main() {
// nama library sama dengan nama package
println!("{}", greeter::greet("Ferris"));
}

Kenapa dipisah seperti ini? Kode di lib.rs mudah di-test, dan program lain (atau binary kedua) bisa memakainya ulang. Playground hanya punya satu file, tapi kamu bisa merasakan hal yang sama dengan pub mod, seperti di halaman Modul di Rust:

mod greeter {
pub fn greet(name: &str) -> String {
format!("Halo, {name}!")
}
}
fn main() {
println!("{}", greeter::greet("Ferris"));
}

Output:

Halo, Ferris!
▶ Coba sendiri

Saat proyek berkembang menjadi beberapa package yang saling berkaitan (misalnya aplikasi command line, web server, dan library bersama), kamu bisa menaruhnya dalam satu workspace. Semuanya berbagi satu Cargo.lock dan satu folder target/, jadi semuanya di-build bersama dan memakai versi dependency yang sama.

Cargo.toml di tingkat paling atas berisi daftar anggotanya:

[workspace]
members = ["app", "core"]
resolver = "3"
my_project/
├── Cargo.toml # file workspace di atas
├── Cargo.lock # dipakai bersama oleh semua anggota
├── app/ # package binary
└── core/ # package library

Lalu cargo build di tingkat atas akan mem-build semuanya, dan cargo run -p app menjalankan satu anggota. Kamu belum butuh ini untuk proyek-proyek pertamamu, tapi ada baiknya kamu mengenalinya saat membaca kode orang lain.

Memakai crate tanpa menambahkannya. Kalau kamu menulis use serde_json::json; tapi lupa cargo add serde_json, compiler tidak bisa menemukannya:

error[E0432]: unresolved import `serde_json`
--> src/main.rs:1:5
|
1 | use serde_json::json;
| ^^^^^^^^^^ use of unresolved module or unlinked crate `serde_json`
|
= help: you might be missing a crate named `serde_json`

Compiler bilang import serde_json tidak ditemukan, dan mungkin kamu belum punya crate dengan nama itu. Solusinya, jalankan cargo add serde_json. Cek juga ejaannya: nama crate memakai - atau _, dan di dalam kode, - selalu berubah menjadi _.

Lupa menyalakan feature. Kalau #[derive(Serialize)] bilang tidak bisa menemukan derive macro, kemungkinan kamu menambahkan serde tanpa --features derive. Jalankan lagi cargo add serde --features derive; perintah ini akan memperbarui baris yang sudah ada.

Mengukur kecepatan dengan build debug. Kalau programmu terasa lambat, coba cargo run --release dulu sebelum mengubah kode apa pun.

Gunakan makro json! dari serde_json untuk mendeskripsikan sebuah crate dengan tiga key: downloads (angka), name, dan version. Cetak seluruh nilainya, lalu cetak versinya saja.

Petunjuk: tiru bentuk contoh crab di Menambahkan dependency dengan cargo add. Di komputermu sendiri, jalankan cargo add serde_json terlebih dahulu; Playground sudah memilikinya.

Lihat solusi
use serde_json::json;
fn main() {
let krate = json!({
"downloads": 500,
"name": "serde_json",
"version": "1.0"
});
println!("{}", krate);
println!("Versi: {}", krate["version"]);
}

Output:

{"downloads":500,"name":"serde_json","version":"1.0"}
Versi: "1.0"
▶ Coba sendiri

Variabelnya diberi nama krate karena crate adalah kata yang dicadangkan (reserved word) di Rust. Ejaan ini lelucon yang umum di kode Rust. Seperti sebelumnya, versinya dicetak dengan tanda kutip karena masih berupa nilai JSON; halaman JSON dengan Serde menunjukkan cara mengambil teks biasanya.

Tulis fungsi debug_log(msg: &str) yang mencetak [debug] diikuti pesannya, tapi hanya di build debug. Pakai fungsi itu di main di sekitar perhitungan kecil, lalu cetak hasilnya seperti biasa.

Petunjuk: bungkus println! dengan if cfg!(debug_assertions) { ... }, seperti di Build debug vs release.

Lihat solusi
fn debug_log(msg: &str) {
if cfg!(debug_assertions) {
println!("[debug] {msg}");
}
}
fn main() {
debug_log("memulai perhitungan");
let mut total = 0;
for n in 1..=10 {
total += n;
}
debug_log("perhitungan selesai");
println!("Jumlah 1 sampai 10 adalah {total}");
}

Output:

[debug] memulai perhitungan
[debug] perhitungan selesai
Jumlah 1 sampai 10 adalah 55
▶ Coba sendiri

Ganti Playground ke “Release” (atau pakai cargo run --release di komputermu) lalu jalankan lagi: hanya baris terakhir yang tercetak. Untuk proyek sungguhan, halaman Logging di Rust menunjukkan crate yang menangani ini dengan benar, lengkap dengan level seperti debug dan info.

  • Cargo mem-build, menjalankan, menguji, dan merapikan format kodemu, serta mengunduh crate dari crates.io.
  • Cargo.toml berisi apa yang kamu minta; Cargo.lock mencatat versi persis yang kamu dapat. Commit keduanya.
  • cargo add nama --features x menambahkan dependency. "1.0" berarti “versi 1.x apa pun yang kompatibel”.
  • Nilai sebuah crate dari jumlah unduhan, update terbaru, dan dokumentasi yang bagus di docs.rs.
  • Pakai cargo check selagi menulis kode, cargo fmt dan cargo clippy sebelum commit, dan --release untuk kecepatan.
  • Taruh logika yang bisa dipakai ulang di src/lib.rs, dan biarkan src/main.rs tetap kecil.

Kuis singkat

1. Di Cargo.toml, apa arti serde_json = "1.0"?

2. File mana yang mencatat versi persis dari setiap dependency yang dipakai build-mu?

3. Kamu ingin mengecek error di kodemu secepat mungkin, tanpa menghasilkan program. Perintah mana yang paling cocok?

4. Bagaimana cara menambahkan serde dengan feature derive yang menyala?

Skor: 0 / 4