類型別名
類型別名 (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
特徵用得相當多。我們會在之後都學到 pub
和 Deref
。
在函式中匯入和重新命名
通常你會在程式的頂端寫 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), // 這比較好一點 // ⚠️ } }
我們已經看到 ::*
的意思是"匯入在 :: 之後的所有內容"。在我們的例子中,這意味著匯入 North
、NorthEast
、......一直到 NorthWest
。你也可以在你匯入別人的程式碼時這樣做,但如果程式碼非常大,你可能會遇到問題。要是它有一些元素和你的程式碼是一樣的呢?所以一般情況下,除非你有把握最好是不要一直使用::*
。很多時候你在別人的程式碼裡看到一個叫 prelude
的部分,裡面有你可能需要的所有主要元素。那麼你通常會這樣使用:name::prelude::*
。我們將會在 modules
和 crates
的章節中講到更多。
您也可以使用 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
。