屬性

你之前有見過 #[derive(Debug)] 這樣的程式碼:這種類型的程式碼叫做 屬性(Attribute)。這些屬性是能提供資訊給編譯器的小塊程式碼。它們雖然不容易建立,但使用起來非常方便。如果你只用 # 來寫屬性,那麼它將影響下一行的程式碼。但如果你是用 #! 來寫,那麼將影響它自己空間裡的一切。

這裡是一些你會經常見到的屬性:

#[allow(dead_code)]#[allow(unused_variables)]。如果你寫了用不到的程式碼,Rust 仍然會編譯,但會讓你知道。例如這裡是裡面什麼都沒有結構體和一個變數。它們任何一個我們都沒有用。

struct JustAStruct {}

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

如果你這樣寫,Rust 會提醒你你沒有使用它們:

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

我們知道可以在名字前面寫 _,讓編譯器安靜下來:

struct _JustAStruct {}

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

但你也可以使用屬性。你會注意到在訊息中,它使用了 #[warn(unused_variables)]#[warn(dead_code)]。在我們的程式碼中,JustAStruct 是死程式碼(dead code),而 some_char 是個未使用的變數。warn 的反面是 allow,所以我們可以這樣寫,它就不會再說什麼了:

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

struct Struct1 {} // 做五個結構體
struct Struct2 {}
struct Struct3 {}
struct Struct4 {}
struct Struct5 {}

fn main() {
    let char1 = 'ん'; // 還有四個變數. 我們不使用它們任何一個但編譯器安靜了
    let char2 = ';';
    let some_str = "I'm just a regular &str";
    let some_vec = vec!["I", "am", "just", "a", "vec"];
}

當然,處理死程式碼和未使用的變數是很重要的。但有時你希望編譯器安靜一段時間。或者是你可能需要展示一些程式碼或教人們 Rust,但又不想讓編譯器訊息來迷惑他們的時候。

#[derive(TraitName)] 讓你可以給你建立的結構和列舉推導出一些特徵。這適用於許多可以被自動推導的常見特徵。有些像 Display 這樣的特徵不能自動推導,因為對於 Display,你必須選擇如何去顯示:

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

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

錯誤訊息會告訴你:

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

但是對於可以自動推匯出的特徵,你可以隨心所欲的放進去。讓我們在一行裡加入七個特徵給 HoldsAString,當然只是為了好玩,儘管它只需要一個。

#[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);
}

另外,如果(也只有在)結構體的所有欄位都實作了 Copy 的情況下,你才可以讓結構體是 Copy 的。HoldsAString 裡的 String 不是 Copy,所以你不能對它使用 #[derive(Copy)]。但是對下面這個結構是可以的:

#[derive(Clone, Copy)] // 你也需要 Clone 來使用 Copy
struct NumberAndBool {
    number: i32, // i32 是 Copy
    true_or_false: bool // bool 也是 Copy. 所以沒問題
}

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); // 如果它不能拷貝, 這裡會造成錯誤
}

#[cfg()] 的意思是組態,告訴編譯器是否執行程式碼。它通常是像這樣的:#[cfg(test)]。你會在寫測試函式的時候用到,這樣它就知道不要執行它們除非你在跑測試。那麼你可以在你的程式碼附近寫測試,但編譯器不會執行它們,除非你告訴它這麼做。

另一個會使用 cfg 的例子是 #[cfg(target_os = "windows")]。有了它,你可以告訴編譯器只能在 Windows 上執行程式碼,Linux 或其他平臺則不能。

#![no_std] 是個有趣的屬性,它告訴 Rust 不要引入標準函式庫。這表示你沒有 VecString 以及標準函式庫中的其他任何東西可以用。你會在那些沒有多少記憶體或空間的小型裝置的程式碼中看到這個。

你可以在這裡看到更多的屬性。