類型別名

類型別名 (Type alias) 的意思是"給某個型別新名字"。類型別名非常簡單。通常你會使用在有個很長的型別,而又不想每次都寫它時。或是當你想給型別取個更好的名字方便記憶時,也可以使用它。這裡有兩個類型別名的範例。

這裡的型別不難,但是你想讓你的程式碼更容易被其他人(或者你自己)理解:

type CharacterVec = Vec<char>;

fn main() {}

這裡是種非常難以閱讀的型別:

// 這個回傳型別超長
fn returns<'a>(input: &'a Vec<char>) -> std::iter::Take<std::iter::Skip<std::slice::Iter<'a, char>>> {
    input.iter().skip(4).take(5)
}

fn main() {}

所以你可以改成這樣:

type SkipFourTakeFive<'a> = std::iter::Take<std::iter::Skip<std::slice::Iter<'a, char>>>;

fn returns<'a>(input: &'a Vec<char>) -> SkipFourTakeFive {
    input.iter().skip(4).take(5)
}

fn main() {}

當然你也可以匯入型別,讓它更短:

use std::iter::{Take, Skip};
use std::slice::Iter;

fn returns<'a>(input: &'a Vec<char>) -> Take<Skip<Iter<'a, char>>> {
    input.iter().skip(4).take(5)
}

fn main() {}

所以你可以根據自己的喜好來決定呈現你的程式碼的最佳方式。

請注意這並沒有建立實際的新型別。它只是替代現有型別的名稱。所以如果你寫了 type File = String;,編譯器只會看到 String。所以將會印出 true

type File = String;

fn main() {
    let my_file = File::from("I am file contents");
    let my_string = String::from("I am file contents");
    println!("{}", my_file == my_string);
}

那麼如果你想要實際的新型別呢?

如果你想要編譯器看到的是 File 的新檔案型別,你可以把它放在結構體中。(這是所謂的 newtype 慣用寫法)

struct File(String); // File 是個對 String 的封裝

fn main() {
    let my_file = File(String::from("I am file contents"));
    let my_string = String::from("I am file contents");
}

現在這樣就不能執行了,因為它們是兩種不同的型別:

struct File(String); // File 是個對 String 的封裝

fn main() {
    let my_file = File(String::from("I am file contents"));
    let my_string = String::from("I am file contents");
    println!("{}", my_file == my_string);  // ⚠️ 無法比較 File 和 String
}

如果你想比較裡面的 String,可以用 my_file.0

struct File(String);

fn main() {
    let my_file = File(String::from("I am file contents"));
    let my_string = String::from("I am file contents");
    println!("{}", my_file.0 == my_string); // my_file.0 是個 String, 因此印出 true
}

並且現在這個型別沒有任何特徵,所以你自己可以實作它們。這並不會太意外:

#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
struct File(String);
}

那麼當你使用這裡的 File 型別時,你可以克隆它和用 Debug 印出它,但它不會有 String 的特徵,除非你用 .0 來取得它裡面的 String。但是在其他人的程式碼中,如果它被標記為 pub 公開使用時,你就只能用 .0。而且那也是為什麼這些不同種類的型別會用 Deref 特徵用得相當多。我們會在之後都學到 pubDeref

在函式中匯入和重新命名

通常你會在程式的頂端寫 use,像這樣:

use std::cell::{Cell, RefCell};

fn main() {}

但我們會看到,你可以在任何地方這樣做,特別是在函式中使用名稱較長的例舉。像這裡的範例:

enum MapDirection {
    North,
    NorthEast,
    East,
    SouthEast,
    South,
    SouthWest,
    West,
    NorthWest,
}

fn main() {}

fn give_direction(direction: &MapDirection) {
    match direction {
        MapDirection::North => println!("You are heading north."),
        MapDirection::NorthEast => println!("You are heading northeast."),
        // 還剩下相當多要打字...
        // ⚠️ 因為我們沒寫出每個可能出現的變體
    }
}

所以現在我們要在函數裡面匯入 MapDirection。也就是說,在函數里面你可以直接寫 North 等變體名稱。

enum MapDirection {
    North,
    NorthEast,
    East,
    SouthEast,
    South,
    SouthWest,
    West,
    NorthWest,
}

fn main() {}

fn give_direction(direction: &MapDirection) {
    use MapDirection::*; // 匯入 MapDirection 裡的所有東西
    let m = "You are heading";

    match direction {
        North => println!("{} north.", m),
        NorthEast => println!("{} northeast.", m),
        // 這比較好一點
        // ⚠️
    }
}

我們已經看到 ::* 的意思是"匯入在 :: 之後的所有內容"。在我們的例子中,這意味著匯入 NorthNorthEast、......一直到 NorthWest。你也可以在你匯入別人的程式碼時這樣做,但如果程式碼非常大,你可能會遇到問題。要是它有一些元素和你的程式碼是一樣的呢?所以一般情況下,除非你有把握最好是不要一直使用::*。很多時候你在別人的程式碼裡看到一個叫 prelude 的部分,裡面有你可能需要的所有主要元素。那麼你通常會這樣使用:name::prelude::*。我們將會在 modulescrates 的章節中講到更多。

您也可以使用 as 來更改名稱。例如,也許你正在使用別人的程式碼,而你不能改變列舉中的名稱:

enum FileState {
    CannotAccessFile,
    FileOpenedAndReady,
    NoSuchFileExists,
    SimilarFileNameInNextDirectory,
}

fn main() {}

那麼你就能 1) 匯入所有東西 並且 2) 更改名稱:

enum FileState {
    CannotAccessFile,
    FileOpenedAndReady,
    NoSuchFileExists,
    SimilarFileNameInNextDirectory,
}

fn give_filestate(input: &FileState) {
    use FileState::{
        CannotAccessFile as NoAccess,
        FileOpenedAndReady as Good,
        NoSuchFileExists as NoFile,
        SimilarFileNameInNextDirectory as OtherDirectory
    };
    match input {
        NoAccess => println!("Can't access file."),
        Good => println!("Here is your file"),
        NoFile => println!("Sorry, there is no file by that name."),
        OtherDirectory => println!("Please check the other directory."),
    }
}

fn main() {}

所以現在你可以寫成 OtherDirectory 而不是FileState::SimilarFileNameInNextDirectory