# Rust 讀書會 Week 4 [toc] ## CH5 ## CH10 ### 10-1 泛型資料型別 用法:用 <> 標示,這邊聲明一個泛型 `T`,可以讓 compiler 自己去檢查。 **枚舉** ```rust= enum Option<T> { Some(T), None, } ``` **結構體** 注意,impl method 是附加的形式 1. 所有的 type 都會有 `x()`, `y()` 2. `warn()` 只有 `&str` 才有 > Point\<i32\> 有 x, y 兩個 method > Point\<&str\> 有 x, y, warn 三個 method ```rust= struct Point<T> { x: T, y: T, } // 泛型資料的 method impl<T> Point<T> { fn x(&self) -> &T { &self.x } fn y(&self) -> &T { &self.y } } // 專門為特定的型別實做 method impl Point<&str> { fn warn(&self) { println!("Don't use &str, String"); } } fn main() { let p = Point { x: "10", y: "20" }; let q = Point { x: 10, y: 20 }; p.warn(); println!("x: {}, y: {}", p.x(), p.y()); // q.warn(); // error println!("x: {}, y: {}", p.x(), p.y()); } ``` --- ### 10-2 trait Trait 用於定義共同行為,我們可以使用特徵定義來抽象出共同行為。類似於其他語言的 interface。 用法:`trait` 關鍵字 以 Point 為範例,現在我們要推廣二維到三維,然後要計算點到 origin 的距離。二維三維都能計算到原點的距離,所以我們把這個特徵抽出來,取名叫 `Utils`。然後先為 `Point2D` 實做它,如何實做寫在範例裡面。 **Point 範例 - Version 1** ```rust= trait Utils { fn distance_to_origin(&self) -> f32; } struct Point2D { x: f32, y: f32, } struct Point3D { x: f32, y: f32, z: f32, } impl Utils for Point2D { fn distance_to_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } fn main() { let p2d = Point2D { x:3f32, y:4f32 }; println!("{}", p2d.distance_to_origin()); } ``` 阿你說 3D 呢,懶得實做它 compiler 會過不去。所以我們先 implement 一個暫時的。在 `Utils` 補充完這個 function,這個方式稱作為**預設實做**,如果型別沒有實做的話,就會用預設實做。 **Point 範例 - Version 2** ```rust= trait Utils { fn distance_to_origin(&self) -> f32 { println!("not implement yet"); 0f32 } } struct Point2D { x: f32, y: f32, } struct Point3D { x: f32, y: f32, z: f32, } impl Utils for Point2D { fn distance_to_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } impl Utils for Point3D {} fn main() { let p2d = Point2D { x:3f32, y:4f32 }; println!("{}", p2d.distance_to_origin()); let p3d = Point3D { x:3f32, y:4f32, z:5f32 }; println!("{}", p3d.distance_to_origin()); } ``` --- 在其他語言中,我們也可以看到 interface 在作為參數傳遞。當然 trait 也可以。範例中,兩種寫法是一樣的,一個透過 argument,一個透過聲明 `T`。但是你會發現,這樣 compiler 不會過,因為 compiler 沒辦法確認每個傳入的 type 是不是都有實做 trait,所以預設不會有任何 trait 在裡面。 **trait 作為參數傳入 - Version1** ```rust= // way 1 fn print_point(t: &impl Utils) { println!("{:?}", t); } // way 2 fn print_point<T: Utils>(t: &T) { println!("{:?}", t); } ``` 好吧,我們來看一下 Rust 如何印出資料。Rust 印出資料都是透過 trait,像是 `println!("{}")` 會使用的 `Display` 這個 trait。我們常用的 `println!("{:?}")`、`println!("{:#?}")`,則是使用的 `Debug`。 V1 的版本沒有 `Debug`,我們來修改一下。 **trait 作為參數傳入 - Version2** ```rust= // 引入型別 use std::fmt::Debug; // 利用 derive 讓型別利用 Debug #[derive(Debug)] struct Point2D { x: f32, y: f32, } #[derive(Debug)] struct Point3D { x: f32, y: f32, z: f32, } // 現在型別有兩個 trait 了,補上 trait,利用 + fn print_point<T: Utils + Debug>(t: &T) { println!("{:?}", t); } ``` 當你的 trait 用來越多,你也可以使用 `where` ```rust= fn print_point<T>(t: &T) where T: Utils + Debug, { println!("{:?}", t); } ``` interface 能作為參數傳入,當然也可以作為回傳值。 ```rust= fn new_point_2d(x: f32, y: f32) -> (impl Utils + Debug) { Point2D { x, y, } } ``` --- 透過特徵界限來選擇性實作方法,我們在泛型型別可以針對特定型別實做,Trait 也可以。 ```rust= use std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } } ``` [rust playground](https://play.rust-lang.org/?code=fn%20main()%20%7B%0A%20%20%20%20let%20more_numbers%20%3D%20%5B10%2C%2012%2C%2018%5D%3B%0A%20%20%20%20let%20mut%20all_numbers%20%3D%20Vec%3A%3Anew()%3B%0A%20%20%20%20all_numbers.push(1)%3B%0A%20%20%20%20all_numbers.push(2)%3B%0A%20%20%20%20all_numbers.extend(%26more_numbers)%3B%0A%20%20%20%20%0A%20%20%20%20println!(%22%7B%3A%3F%7D%22%2C%20all_numbers)%3B%0A%7D&version=stable) --- ### 10-3 (暫補)