Attributes

Anda telah melihat code seperti #[derive(Debug)] sebelumnya: code seperti ini disebut sebagai attribute. Attribute adalah bagian kecil dari code yang meberikan informasi ke compiler. Attribute ini tidaklah mudah untuk dibuat, tetapi mereka sangat mudah untuk digunakan. Jika Anda menulis sebuah attribute dengan menggunakan #, maka itu akan mempengaruhi code di baris berikutnya. Tapi jika Anda menuliskannya dengan #! maka ia akan mempengaruhi segala sesuatu di lingkupnya sendiri.

Ini adalah beberapa attributes yang akan sering Anda lihat:

#[allow(dead_code)] dan #[allow(unused_variables)]. Jika Anda menulis code yang tidak Anda gunakan, Rust akan tetap melakukan compile Tapi ia akan memberitahukannya ke Anda. Sebagai contoh, di sini ada struct yang tidak memiliki apapun di dalamnya, dan sebuah variabel. Kita tidak menggunakan keduanya.

struct JustAStruct {}

fn main() {
    let some_char = 'ん';
}

Jika Anda menuliskan ini, Rust akan mengingatkan Anda bahwa Anda tidak menggunakan mereka:

warning: unused variable: `some_char`
 --> src\main.rs:4:9
  |
4 |     let some_char = 'ん';
  |         ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_some_char`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: struct is never constructed: `JustAStruct`
 --> src\main.rs:1:8
  |
1 | struct JustAStruct {}
  |        ^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

Kita tahu bahwa kita bisa menuliskan sebuah _ (underscore) sebelum namanya untuk membuat compilernya tidak memberikan warning:

struct _JustAStruct {}

fn main() {
    let _some_char = 'ん';
}

Tapi Anda bisa menggunakan attribute. Anda akan melihat pesan dari code di atas, bahwa disarankan untuk menggunakan #[warn(unused_variables)] dan #[warn(dead_code)]. Di code tersebut, JustAStruct adalah dead code, dan some_char adalah unused variable (variabel yang tidak digunakan). Kebalikan dari warn adalah allow, sehingga kita bisa menuliskan ini dan compilernya tidak akan memberikan warning apapun:

#![allow(dead_code)]
#![allow(unused_variables)]

struct Struct1 {} // Buat lima buah struct
struct Struct2 {}
struct Struct3 {}
struct Struct4 {}
struct Struct5 {}

fn main() {
    let char1 = 'ん'; // dan empat buah variabel. Kita tidak menggunakan satupun dari variabel-variabel ini, tapi compiler tidak akan memberikan warning apapun
    let char2 = ';';
    let some_str = "I'm just a regular &str";
    let some_vec = vec!["I", "am", "just", "a", "vec"];
}

Tentu saja, berurusan dengan dead code dan unused variables sangatlah penting. Tapi terkadang Anda menginginkan compiler "untuk tetap diam" sementara waktu. Atau mungkin Anda perlu menunjukkan beberapa bagian code ke orang lain. Atau mungkin juga Anda ingin mengajarkan orang lain tentang Rust dan Anda tidak ingin membingungkan mereka dengan pesan yang ditampilkan oleh compiler.

#[derive(TraitName)] memungkinkan kita untuk men-derive (menurunkan) beberapa trait untuk struct dan enum yang kita buat. Attribute ini bekerja pada banyak trait-trait umum yang dapat diturunkan secara otomatis. Beberapa seperti Display tidak bisa diturunkan secara otomatis. Karena, untuk Display, Anda perlu memilih bagaimana cara menampilkannya:

// ⚠️
#[derive(Display)]
struct HoldsAString {
    the_string: String,
}

fn main() {
    let my_string = HoldsAString {
        the_string: "Here I am!".to_string(),
    };
}

Pesan errornya akan memberitahu hal tersebut.

error: cannot find derive macro `Display` in this scope
 --> src\main.rs:2:10
  |
2 | #[derive(Display)]
  |

Tapi untuk trait-trait yang bisa secara otomatis diturunkan, Anda bisa memasukkannya sebanyak yang Anda mau. Mari kita berikan HoldsAString tujuh buah trait dalam satu baris (sekedar coba-coba), meskipun sebenarnya Anda hanya membutuhkan satu saja. :D

#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
struct HoldsAString {
    the_string: String,
}

fn main() {
    let my_string = HoldsAString {
        the_string: "Here I am!".to_string(),
    };
    println!("{:?}", my_string);
}

Dan juga, Anda bisa membuat struct dengan type Copy jika (dan hanya jika) semua fieldnya adalah Copy. HoldsAString memiliki String yang mana ia bukanlah Copy, jadi Anda tidak bisa menggunakan #[derive(Copy)] untuk hal ini. Anda bisa membuat struct seperti di bawah ini:

#[derive(Clone, Copy)] // Anda juga membutuhkan Clone untuk menggunakan Copy
struct NumberAndBool {
    number: i32, // i32 adalah Copy
    true_or_false: bool // bool juga adalah Copy. Jadi, tidak ada masalah
}

fn does_nothing(input: NumberAndBool) {

}

fn main() {
    let number_and_bool = NumberAndBool {
        number: 8,
        true_or_false: true
    };

    does_nothing(number_and_bool);
    does_nothing(number_and_bool); // Jika ia bukanlah copy, maka ia akan error
}

#[cfg()] adalah konfigurasi dan memberitahukan ke compiler apakah Anda menjalankan codenya atau tidak. Anda biasanya melihatnya seperti ini: #[cfg(test)]. Anda akan menggunakannya di saat menulis function untuk keperluan testing, sehingga ia tahu untuk tidak menjalankannya kecuali Anda sedang melakukan testing. Anda juga bisa menuliskan testnya setelah menuliskan codenya, namun compiler tidak akan menjalankannya kecuali Anda menyuruh compiler untuk menjalankannya.

Satu contoh lain dalam penggunaan cfg adalah #[cfg(target_os = "windows")]. Dengan attribute itu, Anda bisa memberitahukan compiler untuk hanya menjalankan codenya di Windows, atau Linux, atau yang lainnya.

#![no_std] adalah attribute menarik yang memberitahukan Rust untuk tidak membawa standard library ke dalam code. Ini berarti, Anda tidak bisa menggunakan Vec, String, dan apapun yang ada di dalam standard library. Anda akan sering melihat code seperti ini di dalam code untuk perangkat-perangkat kecil yang tidak memiliki banyak memori atau ruang.

Anda bisa melihat attribute-attribute lainnya disini.