Crates #
Crate adalah unit kompilasi dan distribusi terkecil di Rust — setara dengan package di npm, gem di Ruby, atau module di Go. Semua kode Rust hidup dalam sebuah crate, dan Cargo adalah tool yang mengelola crate: mengunduh dependensi, mengompilasi, menjalankan test, dan mempublikasikan. Memahami sistem crate dan Cargo bukan hanya soal bisa menambah dependensi di Cargo.toml — ada fitur-fitur seperti feature flags, workspace, dan path dependency yang secara signifikan memengaruhi cara kamu menyusun proyek. Artikel ini membahas semuanya dari fondasi hingga pola yang digunakan di proyek produksi nyata, ditutup dengan daftar crate esensial yang hampir selalu ada di proyek Rust.
Dua Jenis Crate #
Rust mengenal dua jenis crate yang menghasilkan output berbeda:
flowchart LR
subgraph Binary Crate
B["src/main.rs\n(crate root)"] --> BE["Binary/Executable\n.exe / file tanpa ekstensi"]
end
subgraph Library Crate
L["src/lib.rs\n(crate root)"] --> LE["Library\n.rlib / .so / .dll"]
end
subgraph Keduanya
BL["src/main.rs\n+\nsrc/lib.rs"] --> BLE["Binary yang\nmenggunakan library-nya sendiri"]
endBinary crate menghasilkan program yang bisa dijalankan. Harus punya fungsi main(). Bisa ada beberapa binary dalam satu paket (di src/bin/).
Library crate menghasilkan kode yang bisa dipakai oleh crate lain. Tidak punya main(). Satu paket hanya bisa punya satu library crate (src/lib.rs).
Sebuah paket bisa mengandung keduanya — library dan satu atau lebih binary yang menggunakan library tersebut:
proyek-ku/
├── Cargo.toml
└── src/
├── lib.rs ← library crate (logika utama)
├── main.rs ← binary utama (pakai lib.rs)
└── bin/
├── server.rs ← binary tambahan
└── migrate.rs ← binary tambahan
Struktur Proyek dan Cargo.toml
#
Cargo.toml adalah jantung dari setiap crate — ia mendefinisikan metadata, dependensi, dan konfigurasi build:
[package]
name = "aplikasi-ku" # Nama crate (harus lowercase, bisa pakai -)
version = "0.1.0" # Semantic versioning: MAJOR.MINOR.PATCH
edition = "2021" # Rust edition: "2015", "2018", atau "2021"
authors = ["Budi <[email protected]>"]
description = "Aplikasi contoh Rust"
license = "MIT OR Apache-2.0"
repository = "https://github.com/budi/aplikasi-ku"
homepage = "https://aplikasi-ku.com"
keywords = ["cli", "utility"]
categories = ["command-line-utilities"]
readme = "README.md"
[dependencies]
# Versi dari crates.io
serde = "1.0"
# Versi dengan fitur tambahan
serde = { version = "1.0", features = ["derive"] }
# Versi spesifik
regex = "1.10.3"
# Range versi
tokio = ">=1.0, <2.0"
[dev-dependencies]
# Hanya digunakan saat testing, tidak dibundel ke binary
tempfile = "3"
mockall = "0.11"
[build-dependencies]
# Digunakan oleh build.rs (build script)
cc = "1"
[features]
# Fitur kondisional — dibahas lebih lanjut nanti
default = ["json"]
json = ["serde/derive", "serde_json"]
async = ["tokio"]
full = ["json", "async"]
[[bin]]
# Binary tambahan selain main.rs
name = "server"
path = "src/bin/server.rs"
[profile.release]
# Optimasi untuk build release
opt-level = 3
lto = true # Link-time optimization
codegen-units = 1 # Lebih lambat compile, binary lebih kecil dan cepat
strip = true # Hapus debug symbols
Mengelola Versi Dependensi #
Cargo menggunakan Semantic Versioning. Cara penulisan versi di Cargo.toml menentukan versi mana yang dianggap kompatibel:
| Spesifikasi | Arti | Versi yang diterima |
|---|---|---|
"1.2.3" | Kompatibel dengan 1.2.3 | >=1.2.3, <2.0.0 |
"^1.2.3" | Sama dengan atas | >=1.2.3, <2.0.0 |
"~1.2.3" | Patch kompatibel | >=1.2.3, <1.3.0 |
"1.2.*" | Minor kompatibel | >=1.2.0, <1.3.0 |
">=1.2, <2" | Range eksplisit | Sesuai range |
"=1.2.3" | Versi tepat | Hanya 1.2.3 |
"*" | Semua versi | Versi terbaru |
Perintah Cargo yang Sering Digunakan #
# Membuat proyek baru
cargo new nama-proyek # binary (default)
cargo new nama-lib --lib # library
# Build dan run
cargo build # debug build
cargo build --release # release build (lebih lama, lebih optimal)
cargo run # build + run
cargo run --bin nama-binary # run binary tertentu
cargo run -- --argumen # teruskan argumen ke program
# Testing
cargo test # semua test
cargo test nama_fungsi # filter test berdasarkan nama
cargo test --doc # hanya doc test
cargo test --release # test dengan optimasi release
# Dependensi
cargo add serde # tambah dependensi (cargo-add, Rust 1.62+)
cargo add serde --features derive
cargo remove serde # hapus dependensi
cargo update # perbarui dependensi ke versi terbaru yang kompatibel
cargo tree # tampilkan pohon dependensi
# Dokumentasi
cargo doc # generate dokumentasi
cargo doc --open # generate + buka di browser
# Linting dan formatting
cargo clippy # jalankan linter (clippy harus diinstal)
cargo fmt # format kode (rustfmt harus diinstal)
cargo check # cek error tanpa build (lebih cepat dari cargo build)
# Publikasi
cargo login # login ke crates.io dengan API token
cargo publish --dry-run # simulasi publikasi tanpa benar-benar publish
cargo publish # publikasikan ke crates.io
Sistem Modul #
Crate dapat diorganisasi menjadi modul bersarang. Modul mengontrol namespace dan visibilitas:
// src/lib.rs — crate root untuk library
// Modul inline
pub mod matematika {
pub fn tambah(a: f64, b: f64) -> f64 {
a + b
}
// Modul bersarang
pub mod trigonometri {
pub fn sin(x: f64) -> f64 {
x.sin()
}
pub fn cos(x: f64) -> f64 {
x.cos()
}
}
// Item private — hanya tersedia di dalam modul ini
fn helper_internal() -> &'static str {
"tidak terekspos ke luar"
}
}
// Modul dari file terpisah — Rust mencari src/geometri.rs atau src/geometri/mod.rs
pub mod geometri;
// Modul dari file dalam subdirektori (Rust 2018+)
// Rust mencari src/io/file.rs
pub mod io {
pub mod file;
}
// src/geometri.rs
pub struct Persegi {
pub sisi: f64,
}
impl Persegi {
pub fn luas(&self) -> f64 {
self.sisi * self.sisi
}
}
// Item non-pub hanya bisa diakses dari dalam modul geometri
fn helper() {}
Visibilitas #
pub mod kontrol {
// pub — publik ke semua
pub fn publik() {}
// tanpa pub — private, hanya dalam modul ini
fn privat() {}
// pub(crate) — publik dalam crate, private ke luar
pub(crate) fn publik_dalam_crate() {}
// pub(super) — publik ke modul parent
pub(super) fn publik_ke_parent() {}
pub mod sub {
// pub(in path) — publik ke path tertentu
pub(in crate::kontrol) fn publik_ke_kontrol() {}
}
}
use untuk Mempersingkat Path
#
// Tanpa use — path penuh setiap kali
fn contoh() {
let map = std::collections::HashMap::<&str, i32>::new();
}
// Dengan use — lebih ringkas
use std::collections::HashMap;
use std::io::{self, Read, Write}; // multiple import
fn contoh() {
let map = HashMap::<&str, i32>::new();
}
// Alias dengan as
use std::collections::HashMap as Map;
use std::fmt::Display as Tampilkan;
// Re-export — ekspos item dari modul lain seakan milik crate ini
pub use crate::geometri::Persegi; // pengguna crate bisa akses langsung
Feature Flags — Dependensi Kondisional #
Feature flags memungkinkan kamu mengaktifkan atau menonaktifkan bagian dari crate berdasarkan konfigurasi build:
# Cargo.toml
[features]
default = ["json"] # fitur yang aktif secara default
json = ["serde_json"] # aktifkan dependensi serde_json
async = ["tokio/full"] # aktifkan fitur "full" dari crate tokio
tls = ["rustls"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true } # hanya jika fitur "json" aktif
tokio = { version = "1", optional = true }
rustls = { version = "0.21", optional = true }
// src/lib.rs — kode kondisional berdasarkan fitur
// Hanya dikompilasi jika fitur "json" aktif
#[cfg(feature = "json")]
pub mod json {
use serde_json::Value;
pub fn parse(s: &str) -> Result<Value, serde_json::Error> {
serde_json::from_str(s)
}
}
// Hanya dikompilasi jika fitur "async" aktif
#[cfg(feature = "async")]
pub async fn fetch(url: &str) -> Result<String, Box<dyn std::error::Error>> {
let resp = reqwest::get(url).await?.text().await?;
Ok(resp)
}
// Kode yang berbeda berdasarkan platform
#[cfg(target_os = "linux")]
fn implementasi_linux() { /* ... */ }
#[cfg(target_os = "windows")]
fn implementasi_windows() { /* ... */ }
#[cfg(debug_assertions)]
fn hanya_di_debug() {
println!("[DEBUG] Data internal: ...");
}
Mengaktifkan feature saat build:
cargo build --features "json,async"
cargo build --all-features # aktifkan semua fitur
cargo build --no-default-features # nonaktifkan default features
cargo build --no-default-features --features "tls"
Workspace — Proyek Multi-Crate #
Workspace memungkinkan beberapa crate hidup dalam satu repositori dengan berbagi satu Cargo.lock dan direktori target:
proyek-besar/
├── Cargo.toml ← workspace root
├── Cargo.lock ← shared lock file
├── target/ ← shared build output
├── crates/
│ ├── core/ ← library crate utama
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ ├── api/ ← server API
│ │ ├── Cargo.toml
│ │ └── src/main.rs
│ └── cli/ ← CLI tool
│ ├── Cargo.toml
│ └── src/main.rs
└── tools/
└── codegen/
├── Cargo.toml
└── src/main.rs
# Cargo.toml di root workspace
[workspace]
members = [
"crates/core",
"crates/api",
"crates/cli",
"tools/codegen",
]
# Dependency bersama yang bisa di-inherit oleh member
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# crates/api/Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2021"
[dependencies]
core = { path = "../core" } # gunakan crate lokal
serde.workspace = true # inherit dari workspace
tokio.workspace = true # inherit dari workspace
# Perintah Cargo untuk workspace
cargo build # build semua member
cargo build -p api # build hanya crate "api"
cargo test --workspace # test semua member
cargo run -p cli -- --help # jalankan binary dari crate "cli"
Membuat dan Mempublikasikan Crate #
Langkah-langkah mempublikasikan crate ke crates.io:
# 1. Pastikan Cargo.toml lengkap
# (name, version, description, license, repository wajib ada)
# 2. Tulis dokumentasi yang baik (/// di setiap item publik)
cargo doc --open # preview dokumentasi
# 3. Jalankan semua test
cargo test --all-features
# 4. Cek tidak ada warning
cargo clippy
# 5. Login ke crates.io
cargo login [API_TOKEN]
# API Token bisa didapat dari https://crates.io/me
# 6. Dry run — simulasi tanpa benar-benar publish
cargo publish --dry-run
# 7. Publish!
cargo publish
# Cargo.toml minimal untuk publish
[package]
name = "nama-crate-ku"
version = "0.1.0"
edition = "2021"
description = "Deskripsi singkat tentang crate ini"
license = "MIT"
repository = "https://github.com/user/nama-crate-ku"
Setelah crate dipublikasikan ke crates.io, versi tersebut tidak bisa dihapus atau diubah — hanya bisa di-yank (ditandai jangan digunakan tapi masih bisa diunduh). Pastikan kode sudah benar sebelum publish. Gunakan --dry-run untuk memverifikasi.Crate Esensial yang Wajib Diketahui #
Berikut crate-crate yang paling sering muncul di proyek Rust produksi, dikelompokkan berdasarkan kategori:
Serialisasi dan Data #
| Crate | Fungsi | Cargo.toml |
|---|---|---|
serde + serde_json | Serialisasi/deserialisasi JSON, YAML, TOML, dll. | serde = { version = "1", features = ["derive"] } |
toml | Parse dan tulis file TOML | toml = "0.8" |
csv | Baca dan tulis CSV | csv = "1" |
// serde — serialisasi/deserialisasi
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Debug, Serialize, Deserialize)]
struct Pengguna {
nama: String,
usia: u8,
aktif: bool,
}
fn main() {
let user = Pengguna { nama: "Budi".into(), usia: 28, aktif: true };
// Struct → JSON string
let json = serde_json::to_string_pretty(&user).unwrap();
println!("{}", json);
// JSON string → Struct
let kembali: Pengguna = serde_json::from_str(&json).unwrap();
println!("{:?}", kembali);
}
Async Runtime dan Networking #
| Crate | Fungsi | Cargo.toml |
|---|---|---|
tokio | Async runtime paling populer | tokio = { version = "1", features = ["full"] } |
reqwest | HTTP client (sync dan async) | reqwest = { version = "0.11", features = ["json"] } |
axum | Web framework berbasis tokio | axum = "0.7" |
// tokio + reqwest — HTTP request asinkron
use reqwest;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = reqwest::get("https://httpbin.org/ip")
.await?
.json::<serde_json::Value>()
.await?;
println!("IP kamu: {}", resp["origin"]);
Ok(())
}
Error Handling #
| Crate | Fungsi | Cargo.toml |
|---|---|---|
anyhow | Error handling mudah untuk aplikasi | anyhow = "1" |
thiserror | Tipe error kustom dengan derive | thiserror = "1" |
// anyhow — sangat ringkas untuk aplikasi
use anyhow::{Context, Result};
fn baca_konfigurasi(path: &str) -> Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("Gagal membaca file '{}'", path))
}
fn main() -> Result<()> {
let config = baca_konfigurasi("config.toml")?;
println!("{}", config);
Ok(())
}
// thiserror — tipe error kustom yang ekspresif untuk library
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("File tidak ditemukan: {0}")]
FileNotFound(String),
#[error("Parse gagal: {0}")]
ParseError(#[from] std::num::ParseIntError),
#[error("Koneksi gagal setelah {detik} detik")]
ConnectionTimeout { detik: u64 },
}
Logging #
| Crate | Fungsi | Cargo.toml |
|---|---|---|
log | Facade logging (API abstrak) | log = "0.4" |
env_logger | Implementasi log ke stderr | env_logger = "0.10" |
tracing | Structured logging dan tracing | tracing = "0.1" |
// log + env_logger
use log::{debug, error, info, warn};
fn main() {
env_logger::init(); // baca RUST_LOG env var
info!("Aplikasi dimulai");
debug!("Detail debug: nilai={}", 42);
warn!("Peringatan: memori hampir penuh");
error!("Error kritis: {}", "koneksi gagal");
}
// Jalankan dengan: RUST_LOG=debug cargo run
Utilitas Umum #
| Crate | Fungsi |
|---|---|
rand | Angka acak |
chrono | Tanggal dan waktu |
regex | Regular expression |
uuid | Generate UUID |
clap | CLI argument parsing |
rayon | Paralelisasi data |
itertools | Iterator tambahan |
once_cell | Lazy statics (pre-OnceLock) |
Ringkasan #
- Dua jenis crate — binary (punya
main(), menghasilkan executable) dan library (tidak punyamain(), bisa digunakan crate lain). Satu paket bisa punya keduanya.Cargo.tomladalah kontrak crate — mendefinisikan nama, versi, dependensi, feature flags, dan profil build. Versi mengikuti semantic versioning.Cargo.lockjangan di-gitignore untuk binary — menjamin reproducible build. Untuk library,Cargo.lockbiasanya di-gitignore agar pengguna mendapat versi terbaru yang kompatibel.- Sistem modul menggunakan file dan direktori —
mod nama;dilib.rsmencarisrc/nama.rsatausrc/nama/mod.rs. Semua item private by default, tambahkanpubuntuk ekspos.- Feature flags untuk dependensi opsional —
#[cfg(feature = "nama")]mengkompilasi kode hanya jika fitur aktif. Berguna untuk membuat crate modular tanpa memaksa dependensi yang tidak dibutuhkan semua pengguna.- Workspace untuk proyek besar — beberapa crate dalam satu repositori berbagi satu
Cargo.lockdan direktoritarget. Hindari duplikasi dependensi denganworkspace.dependencies.serdeadalah crate yang hampir selalu ada — serialisasi/deserialisasi ke JSON, YAML, TOML, dan format lain.#[derive(Serialize, Deserialize)]menghemat puluhan baris kode.anyhowuntuk aplikasi,thiserroruntuk library — keduanya menyederhanakan error handling tapi dengan cara berbeda:anyhowuntuk “saya tidak peduli tipe error persis”,thiserroruntuk “saya perlu tipe error yang terstruktur untuk pengguna library”.- Publish tidak bisa di-undo — gunakan
cargo publish --dry-runsebelum publish nyata. Versi yang sudah dipublikasikan tidak bisa dihapus.