Cargo secara Mendalam

Feature, profile, dependensi, dan pemeriksaan dengan cargo clippy.

Daftar isi

Topik ini membahas empat kemampuan Cargo untuk mengendalikan hasil pembangunan: feature sebagai saklar kompilasi kondisional, profile yang mengatur tingkat optimasi, penulisan dependensi beserta rentang versinya, serta pemeriksaan gaya kode dengan cargo clippy. Seluruh mekanisme diuji pada proyek percobaan yang dibangun di luar pohon konten dengan berkas yang disalin dari bundle topik. Setelah membacanya, pembaca dapat menghasilkan beberapa varian program dari satu kode sumber, memilih tingkat optimasi yang sesuai untuk kerja harian maupun rilis, dan memperbaiki temuan clippy pada proyek sendiri.

Feature sebagai saklar kompilasi#

Feature adalah saklar bernama yang mengaktifkan atau menonaktifkan bagian kode pada saat pembangunan. Deklarasinya berada pada seksi [features] di Cargo.toml berupa pemetaan nama feature ke daftar item yang diikutkannya, dan feature dengan daftar kosong menjadi saklar murni yang tidak mengaktifkan item lain. Pada kode, feature dipakai melalui atribut #[cfg(feature = "nama")], sehingga compiler membuang cabang yang tidak aktif sebelum pemeriksaan tipe berjalan. Akibatnya, dua hasil pembangunan dengan feature berbeda benar-benar memuat kode yang berbeda, bukan satu program yang memuat seluruh cabang lalu memeriksa kondisi saat berjalan.

Proyek contoh bernama penghitung-energi memakai dua feature. Feature satuan-joule mengubah satuan ringkasan kalori menjadi joule dan dimasukkan ke himpunan bawaan lewat kunci default, sedangkan feature riwayat menambahkan pencetakan setiap kali kalori ditambahkan. Manifest berikut juga memuat seksi [profile.release] yang dibahas terpisah pada bagian profile.

toml Cargo.toml
[package]
name = "penghitung-energi"
version = "0.1.0"
edition = "2021"

[features]
default = ["satuan-joule"]
satuan-joule = []
riwayat = []

[profile.release]
lto = true
codegen-units = 1
rust src/lib.rs
// tidak-dijalankan sendirian oleh rustc karena berkas ini bagian pustaka tanpa fungsi main.

//! Penghitung energi harian dengan feature yang dapat dipilih.

/// Menyimpan total kalori harian.
pub struct CatatanEnergi {
    total_kalori: f64,
}

impl CatatanEnergi {
    /// Membuat catatan kosong.
    pub fn baru() -> Self {
        CatatanEnergi { total_kalori: 0.0 }
    }

    /// Menambah kalori ke total, mencetak riwayat bila feature riwayat aktif.
    pub fn tambah(&mut self, kalori: f64) {
        self.total_kalori += kalori;
        #[cfg(feature = "riwayat")]
        println!("+{kalori:.1} kcal (total {:.1})", self.total_kalori);
    }

    /// Total kalori hari ini.
    pub fn total(&self) -> f64 {
        self.total_kalori
    }

    /// Ringkasan total dalam satuan yang aktif.
    pub fn ringkasan(&self) -> String {
        #[cfg(feature = "satuan-joule")]
        let satuan = format!("total {:.0} J", self.total_kalori * 4184.0);
        #[cfg(not(feature = "satuan-joule"))]
        let satuan = format!("total {:.1} kcal", self.total_kalori);
        satuan
    }
}

/// Menentukan apakah total tergolong hemat di bawah batas kalori.
pub fn hemat(total_kalori: f64, batas: f64) -> bool {
    total_kalori < batas
}
rust src/main.rs
// tidak-dijalankan sendirian oleh rustc karena memakai crate pustaka penghitung_energi dari berkas lain.

use penghitung_energi::CatatanEnergi;

fn main() {
    let mut harian = CatatanEnergi::baru();
    harian.tambah(450.0);
    harian.tambah(620.0);
    println!("{}", harian.ringkasan());
    println!("hemat: {}", penghitung_energi::hemat(harian.total(), 2000.0));
}

Pada src/lib.rs, atribut #[cfg(feature = "riwayat")] menempel pada pernyataan println! di dalam tambah, sehingga baris itu hilang ketika feature mati. Fungsi ringkasan memakai bentuk kedua: dua deklarasi let dengan atribut #[cfg] dan #[cfg(not(...))] menyediakan dua definisi variabel satuan yang saling menggantikan, dan hanya satu yang sampai ke compiler. Komentar // pada awal kedua berkas Rust menyatakan bahwa keduanya tidak dapat dikompilasi sendirian oleh rustc karena saling merujuk lewat crate, sehingga verifikasi eksekusi dilakukan lewat proyek di luar pohon konten.

bash
# siapkan proyek percobaan yang bersih di luar pohon konten
rm -rf /tmp/proyek-energi
cargo new /tmp/proyek-energi --bin -q --vcs none
# salin berkas proyek dari bundle ke posisinya masing-masing
cp Cargo.toml /tmp/proyek-energi/Cargo.toml
cp src/lib.rs /tmp/proyek-energi/src/lib.rs
cp src/main.rs /tmp/proyek-energi/src/main.rs
# jalankan dengan himpunan feature bawaan
cargo run --manifest-path /tmp/proyek-energi/Cargo.toml -q
text Keluaran
total 4476880 J
hemat: true

Keluaran tersebut berasal dari himpunan bawaan. Feature satuan-joule aktif sehingga total 1070.0 kcal dikalikan 4184 dan dicetak sebagai joule, sedangkan tidak ada baris riwayat karena riwayat mati. Nilai true pada baris kedua berasal dari fungsi hemat yang membandingkan total 1070.0 dengan batas 2000.0. Dua perintah berikut mengubah himpunan feature dan membangun ulang programnya.

bash
# jalankan tanpa feature bawaan sehingga ringkasan memakai kcal
cargo run --manifest-path /tmp/proyek-energi/Cargo.toml -q --no-default-features
# jalankan dengan feature tambahan riwayat
cargo run --manifest-path /tmp/proyek-energi/Cargo.toml -q --features riwayat
text Keluaran
total 1070.0 kcal
hemat: true
+450.0 kcal (total 450.0)
+620.0 kcal (total 1070.0)
total 4476880 J
hemat: true

Tanpa feature bawaan, cabang #[cfg(not(feature = "satuan-joule"))] yang aktif, sehingga ringkasan memakai kcal. Penambahan --features riwayat mengembalikan satuan joule dan mencetak dua baris riwayat dari dalam tambah, yaitu baris-baris yang berasal dari panggilan println! yang sebelumnya dibuang compiler. Tabel berikut merangkum opsi pada perintah cargo.

PerintahFeature yang aktif
cargo runhimpunan default
cargo run --no-default-featurestidak ada
cargo run --features riwayathimpunan default ditambah riwayat
cargo run --all-featuresseluruh feature

Profile debug dan release#

Profile adalah kumpulan pengaturan kompilasi yang dipilih berdasarkan nama. Dua profile yang dipakai sehari-hari adalah dev sebagai bawaan kerja harian dan release untuk hasil yang dibagikan. Profile dev memakai opt-level 0 tanpa optimasi tetapi menyertakan informasi debug penuh, sehingga waktu kompilasi pendek dan proses debug tetap nyaman. Profile release menaikkan optimasi ke tingkat 3 dan tidak menyertakan informasi debug, sehingga berkas biner lebih kecil dan lebih cepat tetapi lebih lama dibangun. Perintah cargo build dan cargo run memakai dev, sedangkan cargo build --release memakai release.

Pengaturandevrelease
Tingkat optimasi (opt-level)03
Informasi debugpenuhtidak disertakan
Waktu pembangunansingkatlebih lama
Kinerja binertanpa optimasiteroptimasi

Seksi [profile.release] pada manifest proyek menimpa dua pengaturan bawaan. Nilai lto = true mengaktifkan Link Time Optimization (LTO), yaitu optimasi tambahan yang berjalan setelah seluruh crate ditautkan sehingga pemanggilan fungsi lintas crate dapat disederhanakan. Nilai codegen-units = 1 membatasi compiler menjadi satu unit pembangkitan kode, yang memberi optimizer pandangan penuh atas crate dengan imbalan waktu pembangunan yang lebih lama. Kombinasi keduanya lazim dipilih untuk program yang memang mengejar kinerja, bukan untuk siklus sunting-kompilasi harian.

bash
# bangun versi release dengan pengaturan lto dan codegen-units dari manifest
cargo build --manifest-path /tmp/proyek-energi/Cargo.toml --release -q
# jalankan biner release secara langsung dari direktori target
/tmp/proyek-energi/target/release/penghitung-energi
text Keluaran
total 4476880 J
hemat: true

Berkas biner hasil pembangunan release berada pada target/release/penghitung-energi dan dapat dijalankan langsung tanpa melalui cargo. Keluarannya sama dengan versi dev karena kedua profile tidak mengubah makna program, dan kedua hasil pembangunan hanya berbeda pada kecepatan saat berjalan serta ukuran berkas. Nama profile baru juga dapat dibuat, misalnya [profile.rilis-debug] yang mewarisi pengaturan release lewat kunci inherits lalu menyalakan kembali informasi debug.

Bagian [dependencies] mencatat crate eksternal yang dipakai proyek. Bentuk paling singkat memetakan nama ke string versi yang dibaca sebagai persyaratan rentang, yaitu menerima versi mulai dari yang tertulis sampai sebelum versi mayor berikutnya. Tulisan serde = "1" menerima seluruh rilis 1 mulai dari 1.0.0, sedangkan rand = "0.8" terikat pada seri 0.8 tanpa melompat ke 0.9. Tanda = di depan versi memaksa satu versi persis bila pengulangan hasil pembangunan menuntutnya.

Tulisan pada manifestRentang versi yang diterima
serde = "1"mulai 1.0.0 sampai sebelum 2.0.0
rand = "0.8"mulai 0.8.0 sampai sebelum 0.9.0
regex = "=1.10"tepat 1.10

Bentuk lengkap dengan kurung kurawal membuka pengaturan tambahan. Kunci features memilih feature milik dependensi tersebut, optional = true menjadikannya dependensi opsional yang hanya dikompilasi bila sebuah feature mengaktifkannya, dan default-features = false mematikan himpunan bawaan milik dependensi. Bagian [dev-dependencies] hanya dipakai oleh pengujian, contoh, dan pengukur kinerja sehingga tidak membebani biner akhir.

toml Contoh: dependensi dengan pengaturan tambahan
[dependencies]
serde = { version = "1", features = ["derive"], optional = true }

[features]
json = ["dep:serde"]

[dev-dependencies]
pretty_assertions = "1"

Feature json pada contoh di atas mengaktifkan dependensi serde lewat bentuk dep:serde, sehingga crate itu beserta kodenya tidak ikut dikompilasi ketika json mati. Setelah resolusi, Cargo mencatat versi persis hasil pemilihan pada berkas Cargo.lock, dan berkas tersebut sebaiknya disertakan pada pelacakan versi agar setiap orang mendapat kombinasi yang sama. Perintah berikut mengelola dependensi dari terminal, tetapi memerlukan jaringan ke registri sehingga tidak dijalankan pada lingkungan ini.

bash
# tidak-dijalankan
# tambahkan dependensi baru beserta versi terpasang saat ini
cargo add serde
# naikkan seluruh dependensi ke versi terbaru yang masih dalam rentang manifest
cargo update

Pemeriksaan dengan cargo clippy#

cargo clippy menjalankan pemeriksa tambahan di atas compiler biasa. Alat ini mengaktifkan ratusan lint, yaitu aturan pemeriksaan yang melaporkan pola kode bermasalah, berbelit, atau berpotensi lambat. Compiler menolak kode yang salah menurut aturan bahasa, sedangkan clippy menilai kualitas penulisan: perulangan indeks yang sebaiknya memakai iterator, perbandingan panjang yang sebaiknya memakai is_empty, atau percabangan yang mengembalikan bool secara berbelit. Perintahnya memakai antarmuka yang sama dengan cargo check, peringatan tidak menggagalkan pembangunan secara bawaan, dan setiap temuan dilengkapi penjelasan serta saran perbaikan.

Program berikut lolos pemeriksaan compiler dan berjalan benar, tetapi memuat tiga pola yang disorot clippy. Berkas daftar-belanja.rs dapat pula dikompilasi sendirian oleh rustc karena memuat fungsi main yang utuh.

rust daftar-belanja.rs
//! Daftar belanja versi awal: lolos pemeriksaan compiler, tetapi memicu saran clippy.

fn main() {
    let daftar = ["beras", "gula", "kopi"];

    // perulangan dengan indeks manual
    for i in 0..daftar.len() {
        println!("{}. {}", i + 1, daftar[i]);
    }

    println!("kosong: {}", kosong(&daftar));
}

/// Memeriksa apakah daftar tidak memuat barang.
fn kosong(daftar: &[&str]) -> bool {
    if daftar.len() == 0 {
        true
    } else {
        false
    }
}
text Keluaran
1. beras
2. gula
3. kopi
kosong: false

Pemeriksaan clippy memerlukan proyek Cargo, sehingga berkas tersebut disalin menjadi src/main.rs sebuah proyek percobaan di luar pohon konten.

bash
# siapkan proyek pemeriksaan yang bersih di luar pohon konten
rm -rf /tmp/proyek-clippy
cargo new /tmp/proyek-clippy --bin -q --vcs none
# salin versi awal sebagai program utama proyek
cp daftar-belanja.rs /tmp/proyek-clippy/src/main.rs
# periksa kode dengan clippy
cargo clippy --manifest-path /tmp/proyek-clippy/Cargo.toml -q
text Keluaran
warning: the loop variable `i` is used to index `daftar`
 --> src/main.rs:7:14
  |
7 |     for i in 0..daftar.len() {
  |              ^^^^^^^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#needless_range_loop
  = note: `#[warn(clippy::needless_range_loop)]` on by default
help: consider using an iterator and enumerate()
  |
7 -     for i in 0..daftar.len() {
7 +     for (i, <item>) in daftar.iter().enumerate() {
  |

warning: this if-then-else expression returns a bool literal
  --> src/main.rs:16:5
   |
16 | /     if daftar.len() == 0 {
17 | |         true
18 | |     } else {
19 | |         false
20 | |     }
   | |_____^ help: you can reduce it to: `daftar.len() == 0`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#needless_bool
   = note: `#[warn(clippy::needless_bool)]` on by default

warning: length comparison to zero
  --> src/main.rs:16:8
   |
16 |     if daftar.len() == 0 {
   |        ^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `daftar.is_empty()`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#len_zero
   = note: `#[warn(clippy::len_zero)]` on by default

Peringatan pertama menyasar perulangan for i in 0..daftar.len() yang hanya memakai i untuk mengindeks daftar, dan saran perbaikannya adalah iter().enumerate(). Dua peringatan berikutnya sama-sama menyorot fungsi kosong: perbandingan len() == 0 disarankan menjadi is_empty(), dan percabangan yang mengembalikan true atau false disarankan dipangkas menjadi satu ekspresi perbandingan. Versi berikut menerapkan ketiga saran itu tanpa mengubah perilaku program.

rust daftar-belanja-rapi.rs
//! Daftar belanja versi rapi: perulangan iterator dan pemanggilan is_empty.

fn main() {
    let daftar = ["beras", "gula", "kopi"];

    for (i, barang) in daftar.iter().enumerate() {
        println!("{}. {}", i + 1, barang);
    }

    println!("kosong: {}", kosong(&daftar));
}

/// Memeriksa apakah daftar tidak memuat barang.
fn kosong(daftar: &[&str]) -> bool {
    daftar.is_empty()
}
text Keluaran
1. beras
2. gula
3. kopi
kosong: false

Pemeriksaan ulang dilakukan setelah versi rapi menggantikan versi awal pada proyek percobaan.

bash
# salin versi rapi lalu periksa ulang
cp daftar-belanja-rapi.rs /tmp/proyek-clippy/src/main.rs
cargo clippy --manifest-path /tmp/proyek-clippy/Cargo.toml -q

Perintah yang sama kini selesai tanpa mencetak apa-apa karena tidak ada temuan yang tersisa. Keluaran program tidak berubah antara kedua versi, dan perbaikan hanya menyentuh cara penulisan kode. Lint clippy dikelompokkan menurut dampaknya, misalnya kelompok correctness untuk pola yang nyaris pasti keliru dan kelompok style untuk kebiasaan penulisan. Aturan yang memang tidak relevan pada satu lokasi dapat diredam dengan #[allow(clippy::nama_lint)] disertai alasan pada komentar agar keputusan itu terbaca.

Kesalahan umum#

Menonaktifkan feature bawaan tanpa sengaja. Opsi --no-default-features mematikan seluruh himpunan default, sehingga --no-default-features --features riwayat menghasilkan program tanpa satuan-joule dan ringkasan yang memakai kcal. Bila sebagian feature bawaan memang dibutuhkan, sebutkan feature itu kembali pada --features, misalnya --no-default-features --features satuan-joule,riwayat.

Menaruh seksi [profile.*] pada manifest anggota. Cargo mengabaikan pengaturan profile yang tidak berada pada manifest akar workspace, sehingga penulisan [profile.release] pada anggota tidak berpengaruh dan hanya memunculkan peringatan. Pindahkan pengaturan tersebut ke manifest maya di akar agar berlaku untuk seluruh anggota.

Membiarkan temuan clippy karena program tetap berjalan. Sebagian lint berada pada kelompok correctness yang menandai pola nyaris pasti keliru, dan perilaku yang tampak benar hari ini tidak menjamin pola itu aman pada masukan lain. Perbaiki temuan atau redam aturannya dengan #[allow] disertai alasan, karena temuan yang dibiarkan menumpuk membuat sinyal penting tenggelam di antara yang sepele.

Ringkasan#

  • Feature adalah saklar pada manifest yang bekerja lewat #[cfg(feature = "...")], dengan himpunan bawaan yang dapat dimatikan lewat --no-default-features dan tambahan lewat --features.
  • Profile dev dan release menukar kecepatan pembangunan dengan kinerja biner, dan pengaturannya ditimpa lewat seksi [profile.release] pada manifest akar.
  • Dependensi ditulis dengan persyaratan rentang versi, dapat bersifat opsional, dan diaktifkan lewat feature dengan bentuk dep:nama.
  • cargo clippy melaporkan pola kode bermasalah beserta saran perbaikannya, dan pemeriksaannya dapat dijadikan gerbang lewat -D warnings.

Referensi#