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.
[package]
name = "penghitung-energi"
version = "0.1.0"
edition = "2021"
[features]
default = ["satuan-joule"]
satuan-joule = []
riwayat = []
[profile.release]
lto = true
codegen-units = 1// 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
}// 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.
# 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 -qtotal 4476880 J
hemat: trueKeluaran 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.
# 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 riwayattotal 1070.0 kcal
hemat: true
+450.0 kcal (total 450.0)
+620.0 kcal (total 1070.0)
total 4476880 J
hemat: trueTanpa 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.
| Perintah | Feature yang aktif |
|---|---|
cargo run | himpunan default |
cargo run --no-default-features | tidak ada |
cargo run --features riwayat | himpunan default ditambah riwayat |
cargo run --all-features | seluruh 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.
| Pengaturan | dev | release |
|---|---|---|
Tingkat optimasi (opt-level) | 0 | 3 |
| Informasi debug | penuh | tidak disertakan |
| Waktu pembangunan | singkat | lebih lama |
| Kinerja biner | tanpa optimasi | teroptimasi |
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.
# 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-energitotal 4476880 J
hemat: trueBerkas 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.
Menulis dependensi pada manifest#
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 manifest | Rentang 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.
[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.
# 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 updatePemeriksaan 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.
//! 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
}
}1. beras
2. gula
3. kopi
kosong: falsePemeriksaan clippy memerlukan proyek Cargo, sehingga berkas tersebut disalin menjadi src/main.rs sebuah proyek percobaan di luar pohon konten.
# 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 -qwarning: 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 defaultPeringatan 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.
//! 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()
}1. beras
2. gula
3. kopi
kosong: falsePemeriksaan ulang dilakukan setelah versi rapi menggantikan versi awal pada proyek percobaan.
# 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 -qPerintah 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-featuresdan tambahan lewat--features. - Profile
devdanreleasemenukar 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#
- Features — The Cargo Book — Semantik feature, himpunan bawaan, dan penggabungan feature antar pemakai.
- Profiles — The Cargo Book — Daftar pengaturan tiap profile beserta cara menimpanya.
- Specifying Dependencies — The Cargo Book — Bentuk deklarasi dependensi, persyaratan versi, dan dependensi opsional.
- Usage — The Clippy Book
— Cara menjalankan clippy, penerusan argumen, dan opsi
-D warnings.