impl 特徵

impl 特徵 與泛型類似。你還記得,泛型使用型別 T(或任何其他名稱),來表示在程式編譯時才決定的型別。首先讓我們來看個具體的型別:

fn gives_higher_i32(one: i32, two: i32) {
    let higher = if one > two { one } else { two };
    println!("{} is higher.", higher);
}

fn main() {
    gives_higher_i32(8, 10);
}

印出:10 is higher.

但是這個只接受 i32,所以現在我們要把它做成泛型的。我們需要比較,我們還需要用 {} 列印,所以我們的型別 T 需要具有 PartialOrdDisplay 特徵。記住,這意味著"只接受已經具有 PartialOrdDisplay 的型別"。

use std::fmt::Display;

fn gives_higher_i32<T: PartialOrd + Display>(one: T, two: T) {
    let higher = if one > two { one } else { two };
    println!("{} is higher.", higher);
}

fn main() {
    gives_higher_i32(8, 10);
}

現在我們來看看類似的 impl 特徵。我們可以帶入 impl 特徵 型別,而不是 T 型別。然後它將接受實作該特徵的型別。這幾乎是一樣的:

fn prints_it(input: impl Into<String> + std::fmt::Display) { // 接受能轉換成 String 且具有 Display 的任意型別
    println!("You can print many things, including {}", input);
}

fn main() {
    let name = "Tuon";
    let string_name = String::from("Tuon");
    prints_it(name);
    prints_it(string_name);
}

然而,更有趣的是我們可以回傳 impl 特徵,這讓我們可以回傳閉包,因為它們的函式簽名是特徵。你可以在有使用它們的方法的簽名中見到這點。例如,這是 .map() 的簽名:

#![allow(unused)]
fn main() {
fn map<B, F>(self, f: F) -> Map<Self, F>     // 🚧
    where
        Self: Sized,
        F: FnMut(Self::Item) -> B,
    {
        Map::new(self, f)
    }
}

fn map<B, F>(self, f: F) 的意思是,它接受兩個泛型型別。F 是個從實作 .map() 的容器中取一個元素的函式,B 是該函式的回傳型別。然後在where 之後,我們看到的是特徵界限 (trait bound)。("特徵界限"的意思是"它必須有這個特徵"。)一個是 Sized,接下來是個閉包簽名。它必須是個 FnMut,並在 Self::Item 上做閉包,也就是你給它的疊代器。然後它回傳 B

所以我們可以做同樣的事來回傳閉包。要回傳閉包時,使用 impl,然後是閉包簽名。一旦你回傳它,你就可以像使用函式一樣使用它。這裡的小例子是會根據你輸入的文字給出閉包的函式。如果你輸入 "double" 或 "triple",那麼它就會把它乘以 2 或 3,否則就會給你相同的數字。因為它是閉包,我們可以做任何我們想做的事情,所以我們也印出一段訊息。

fn returns_a_closure(input: &str) -> impl FnMut(i32) -> i32 {
    match input {
        "double" => |mut number| {
            number *= 2;
            println!("Doubling number. Now it is {}", number);
            number
        },
        "triple" => |mut number| {
            number *= 40;
            println!("Tripling number. Now it is {}", number);
            number
        },
        _ => |number| {
            println!("Sorry, it's the same: {}.", number);
            number
        },
    }
}

fn main() {
    let my_number = 10;

    // 做出三個閉包
    let mut doubles = returns_a_closure("double");
    let mut triples = returns_a_closure("triple");
    let mut quadruples = returns_a_closure("quadruple");

    doubles(my_number);
    triples(my_number);
    quadruples(my_number);
}

下面是個有點長的範例。讓我們想像在遊戲中,你的角色面對的是晚上比較強的怪物。我們可以做出叫 TimeOfDay 的列舉來記錄一天的情況。你的角色叫西蒙,有個叫 character_fearf64 的數字。它晚上上升、白天下降。我們將寫個叫 change_fear 的函式來改變他的恐懼,但也會做其他事情,如寫訊息。它大概會是這樣:

enum TimeOfDay { // 只是單純的列舉
    Dawn,
    Day,
    Sunset,
    Night,
}

fn change_fear(input: TimeOfDay) -> impl FnMut(f64) -> f64 { // 這個函式接受 TimeOfDay. 回傳閉包.
                                                             // 我們用 impl FnMut(64) -> f64 來說明它需要
                                                             // 改變值, 並且也給回一樣的型別.
    use TimeOfDay::*; // 所以我們只要寫 Dawn、Day、Sunset、Night
                      // 而不是 TimeOfDay::Dawn、TimeOfDay::Day 等等.
    match input {
        Dawn => |x| { // 這就是我們之後會給予的變數 character_fear
            println!("The morning sun has vanquished the horrible night. You no longer feel afraid.");
            println!("Your fear is now {}", x * 0.5);
            x * 0.5
        },
        Day => |x| {
            println!("What a nice day. Maybe put your feet up and rest a bit.");
            println!("Your fear is now {}", x * 0.2);
            x * 0.2
        },
        Sunset => |x| {
            println!("The sun is almost down! This is no good.");
            println!("Your fear is now {}", x * 1.4);
            x * 1.4
        },
        Night => |x| {
            println!("What a horrible night to have a curse.");
            println!("Your fear is now {}", x * 5.0);
            x * 5.0
        },
    }
}

fn main() {
    use TimeOfDay::*;
    let mut character_fear = 10.0; // 西蒙從 10 開始

    let mut daytime = change_fear(Day); // 這裡做四個閉包在每次我們想改變西蒙的恐懼時去呼叫.
    let mut sunset = change_fear(Sunset);
    let mut night = change_fear(Night);
    let mut morning = change_fear(Dawn);

    character_fear = daytime(character_fear); // 對西蒙的恐懼呼叫閉包. 它們給出訊息並改變恐懼數值.
                                              // 在現實生活我們會有 Character 結構體並把它當方法用,
                                              // 像這樣: character_fear.daytime()
    character_fear = sunset(character_fear);
    character_fear = night(character_fear);
    character_fear = morning(character_fear);
}

印出:

What a nice day. Maybe put your feet up and rest a bit.
Your fear is now 2
The sun is almost down! This is no good.
Your fear is now 2.8
What a horrible night to have a curse.
Your fear is now 14
The morning sun has vanquished the horrible night. You no longer feel afraid.
Your fear is now 7