# 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 (暫補)