Crates

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"]
    end

Binary 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:

SpesifikasiArtiVersi 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 eksplisitSesuai range
"=1.2.3"Versi tepatHanya 1.2.3
"*"Semua versiVersi 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 #

CrateFungsiCargo.toml
serde + serde_jsonSerialisasi/deserialisasi JSON, YAML, TOML, dll.serde = { version = "1", features = ["derive"] }
tomlParse dan tulis file TOMLtoml = "0.8"
csvBaca dan tulis CSVcsv = "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 #

CrateFungsiCargo.toml
tokioAsync runtime paling populertokio = { version = "1", features = ["full"] }
reqwestHTTP client (sync dan async)reqwest = { version = "0.11", features = ["json"] }
axumWeb framework berbasis tokioaxum = "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 #

CrateFungsiCargo.toml
anyhowError handling mudah untuk aplikasianyhow = "1"
thiserrorTipe error kustom dengan derivethiserror = "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 #

CrateFungsiCargo.toml
logFacade logging (API abstrak)log = "0.4"
env_loggerImplementasi log ke stderrenv_logger = "0.10"
tracingStructured logging dan tracingtracing = "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 #

CrateFungsi
randAngka acak
chronoTanggal dan waktu
regexRegular expression
uuidGenerate UUID
clapCLI argument parsing
rayonParalelisasi data
itertoolsIterator tambahan
once_cellLazy statics (pre-OnceLock)

Ringkasan #

  • Dua jenis crate — binary (punya main(), menghasilkan executable) dan library (tidak punya main(), bisa digunakan crate lain). Satu paket bisa punya keduanya.
  • Cargo.toml adalah kontrak crate — mendefinisikan nama, versi, dependensi, feature flags, dan profil build. Versi mengikuti semantic versioning.
  • Cargo.lock jangan di-gitignore untuk binary — menjamin reproducible build. Untuk library, Cargo.lock biasanya di-gitignore agar pengguna mendapat versi terbaru yang kompatibel.
  • Sistem modul menggunakan file dan direktorimod nama; di lib.rs mencari src/nama.rs atau src/nama/mod.rs. Semua item private by default, tambahkan pub untuk 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.lock dan direktori target. Hindari duplikasi dependensi dengan workspace.dependencies.
  • serde adalah crate yang hampir selalu ada — serialisasi/deserialisasi ke JSON, YAML, TOML, dan format lain. #[derive(Serialize, Deserialize)] menghemat puluhan baris kode.
  • anyhow untuk aplikasi, thiserror untuk library — keduanya menyederhanakan error handling tapi dengan cara berbeda: anyhow untuk “saya tidak peduli tipe error persis”, thiserror untuk “saya perlu tipe error yang terstruktur untuk pengguna library”.
  • Publish tidak bisa di-undo — gunakan cargo publish --dry-run sebelum publish nyata. Versi yang sudah dipublikasikan tidak bisa dihapus.

← Sebelumnya: Regex   Berikutnya: Multithreading →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact