Library yang baik melayani pemakai yang berbeda-beda: satu pemakai ingin sapaan santai, pemakai lain butuh sapaan resmi dengan gelar. Menyediakan semuanya sekaligus membebani siapa pun yang hanya butuh sebagian — kode terpakai sedikit, binary membesar. Rust menjawab kebutuhan ini dengan feature: sakelar pada tingkat kompilasi yang menghidupkan atau mematikan bagian crate sesuai permintaan pemakai. Kita sudah melihat daftar feature rand saat menjalankan cargo add pada Chapter sebelumnya; sekarang kita berpindah ke kursi pembuat library, mendeklarasikan feature sendiri di manifest, menggerbangkan kode dengan #[cfg], lalu mengamati dari sisi pemakai bagaimana feature mengubah hasil kompilasi — dan bagaimana kompilator menegur ketika kita lupa menyalakannya.
Masalah: Satu Library, Banyak Kebutuhan #
Bayangkan library sapa untuk aplikasi undangan: fungsi sapa menghasilkan sapaan santai seperti Hai, Budi!, sementara fungsi sapa_formal menghasilkan sapaan resmi seperti Selamat datang, Bapak Rahmat. Aplikasi kantor ingin keduanya, tetapi aplikasi obrolan santai hanya ingin fungsi pertama — logika gelar dan format resmi tidak berguna baginya.
Jawaban yang paling mudah terpikir adalah parameter runtime: satu fungsi dengan argumen mode: bool. Pendekatan itu sah, tetapi biayanya tetap dibayar semua pemakai — kode mode resmi ikut terkompilasi di binary obrolan santai padahal tak pernah dipakai, dan kesalahan menulis mode hanya ketahuan saat program berjalan. Fitur memungkinkan library tetap satu sumber kode dengan hasil kompilasi yang menyesuaikan pemakainya: bagian formal hanya ikut terkompilasi bila feature formal diminta, galat pemakaian tertangkap saat kompilasi, dan binary pemakai hemat hanya memuat kode yang benar-benar dipakainya.
Mendeklarasikan Feature di Manifest #
Deklarasi feature hidup di tabel [features] pada manifest library. Manifest sapa:
[package]
name = "sapa"
version = "0.1.0"
edition = "2024"
[features]
formal = []
[dependencies]Mari kita bedah bagian baru tersebut:
- Baris 6 (
[features]): membuka tabel deklarasi feature. Setiap kunci di bawahnya adalah nama feature yang bisa diminta pemakai. - Baris 7 (
formal = []): mendeklarasikan feature bernamaformal. Nilai array kosong berarti feature ini tidak mengaktifkan feature lain — feature berdiri sendiri. Nilai array dapat berisi nama feature crate lain, misalnyaserde = ["dep:serde"]untuk mengaktifkan dependency opsional; pola itu kita jumpai terus di crate besar.
Nama feature bebas dipilih, tetapi satu nama dipesan sistem: default. Kunci tersebut otomatis berisi feature yang aktif bila pemakai tidak meminta apa pun — kita bahas tuntas setelah bagian berikutnya.
Mengompilasi Kondisional dengan cfg #
Gerbang feature ada di kode: atribut #[cfg(feature = "nama")] membuat item hanya dikompilasi bila feature aktif. Isi src/lib.rs milik sapa:
pub fn sapa(nama: &str) -> String {
format!("Hai, {}!", nama)
}
#[cfg(feature = "formal")]
pub fn sapa_formal(nama: &str, gelar: &str) -> String {
format!("Selamat datang, {} {}.", gelar, nama)
}Bedah kode di atas:
- Baris 1-3 (
pub fn sapa(nama: &str) -> String): fungsi tanpa gerbang — selalu hadir di setiap kompilasi, apa pun feature yang aktif. - Baris 5 (
#[cfg(feature = "formal")]): atribut konfigurasi. Saat featureformaltidak aktif, kompilator memperlakukan item di bawahnya seolah tidak pernah ditulis — bukan sekadar tidak bisa dipanggil, tetapi benar-benar tidak ada dalam output kompilasi. - Baris 6 (
pub fn sapa_formal(nama: &str, gelar: &str) -> String): fungsi bertingkat yang menggabungkan gelar dan nama ke dalam format resmi.
Atribut #[cfg] tidak terbatas pada fungsi: module, impl, use, bahkan field struct semuanya bisa digerbangkan dengan cara yang sama — pola yang kita kenal dari #[cfg(test)] pada Section Testing bekerja dengan mesin yang persis ini. Untuk logika di dalam fungsi, makro cfg!(feature = "formal") mengembalikan nilai bool pada saat kompilasi sehingga cabang yang mati dihapus optimizer, meski item di sekitarnya tetap hadir.
Konsumen Mengaktifkan Feature #
Aplikasi undangan memakai sapa sebagai dependency lokal. Aktifasi feature terjadi di manifest pemakai:
[package]
name = "undangan"
version = "0.1.0"
edition = "2024"
[dependencies]
sapa = { path = "../sapa", features = ["formal"] }Kunci features = ["formal"] pada dependency menyalakan feature tersebut. Kode src/main.rs:
use sapa::{sapa, sapa_formal};
fn main() {
let tamu = ["Budi", "Siti"];
for nama in tamu {
println!("{}", sapa(nama));
}
println!("{}", sapa_formal("Rahmat", "Bapak"));
}Output yang Dihasilkan:
Hai, Budi!
Hai, Siti!
Selamat datang, Bapak Rahmat.Ketika baris features = ["formal"] dihapus dari manifest, fungsi sapa_formal lenyap dari hasil kompilasi library, dan pemanggilannya ditolak:
error[E0432]: unresolved import `sapa::sapa_formal`
--> src/main.rs:1:18
|
1 | use sapa::{sapa, sapa_formal};
| ^^^^^^^^^^^ no `sapa_formal` in the root
|
note: found an item that was configured out
--> /tmp/w5a/sapa/src/lib.rs:6:8
|
5 | #[cfg(feature = "formal")]
| ------------------ the item is gated behind the `formal` feature
6 | pub fn sapa_formal(nama: &str, gelar: &str) -> String {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0432`.
error: could not compile `undangan` (bin "undangan") due to 1 previous errorPesan galat ini istimewa karena mendiagnosisnya sangat tepat: kompilator menemukan item yang cocok, lalu melaporkan bahwa item tersebut terkonfigurasi keluar — frasa the item is gated behind the formal feature langsung menunjuk nama feature yang perlu diaktifkan. Perbaikannya bukan menulis ulang kode, melainkan menambahkan "formal" pada daftar features di manifest.
Fitur Bawaan: default #
Tanpa permintaan apa pun dari pemakai, cargo mengaktifkan satu feature bernama default. Perilaku ini terlihat pada manifest undangan sebelum menyalakan formal — dan kita bisa manfaatkan dengan menautkan formal ke default:
[features]
default = ["formal"]
formal = []Deklarasi tersebut membuat formal aktif secara bawaan, sehingga pemakai biasa mendapat fungsi lengkap. Pemakai yang ingin hemat dapat mematikannya dengan sapa = { path = "../sapa", default-features = false } — percobaan nyata pada kombinasi ini menghasilkan galat E0432 yang sama seperti di atas karena sapa_formal kembali tidak dikompilasi. Perintah cargo tree -e features memperlihatkan keadaan fitur dari sisi dependency:
undangan v0.1.0 (/tmp/w5a/undangan)
└── sapa feature "default"
├── sapa v0.1.0 (/tmp/w5a/sapa)
└── sapa feature "formal"
└── sapa v0.1.0 (/tmp/w5a/sapa)Bagan tersebut menunjukkan feature default milik sapa berisi feature formal — relasi penautan antar-feature tampil sebagai garis bertingkat.
Unifikasi: Feature Menyatu Saat Digabungkan #
Satu kejutan menanti pemakai ganda: feature tidak pernah saling meniadakan, melainkan menyatu. Misalkan crate pengantar bergantung pada sapa dengan features = ["formal"], sementara undangan bergantung pada sapa tanpa feature. Hasil build gabungan tetap mengaktifkan formal untuk semua pihak — percobaan nyata dengan pengantar yang memanggil sapa::sapa_formal berhasil dikompilasi meskipun undangan sendiri tidak meminta feature itu. Cargo menyatukan seluruh permintaan feature dari semua jalur dependency menjadi satu set aktif, karena satu library hanya dikompilasi sekali per build. Konsekuensinya: kode undangan ikut bisa mengimpor sapa_formal — keadaan yang perlu kita sadari ketika membaca cargo tree -e features pada proyek besar.
Praktik yang sehat menyikapi unifikasi dengan dua kebiasaan: pertama, perlakukan API di balik feature sebagai opsional di dokumentasi pemakai — kode yang bergantung pada feature orang lain bisa berhenti dikompilasi ketika dependency pengantar dihapus; kedua, gunakan cargo tree -e features sebelum bingung mengapa fungsi tiba-tiba tersedia atau hilang, karena jawabannya hampir selalu ada di pohon feature.
Ringkasan #
Feature adalah sakelar kompilasi: tabel [features] di manifest mendeklarasikan namanya, atribut #[cfg(feature = "...")] menggerbangkan item yang dihidupkan, pemakai menyalakannya lewat kunci features, dan feature default mengatur keadaan bawaan yang bisa dimatikan dengan default-features = false. Galat E0432 dengan catatan gated behind menuntun perbaikan yang tepat, dan unifikasi membuat seluruh permintaan feature menyatu dalam satu set aktif. Bagian crate yang aktif berubah, tetapi cara cargo mengompilasinya — dev versus release — adalah persoalan tersendiri yang kita bahas pada Chapter berikutnya.