rust一路寫來都會給人一種非常節制且保守的風格, 能不多給功能就不多給功能.這幾個好夥伴幾乎可以說是幾乎是在寫code一開始就必須要用到的.
我們嘗試實現一個函數來更新Token的狀態, 一個隨意自定義的data structure.
pub enum TokenColor {
RED ,
GREEN
}
pub enum Size {
BIG,
MID
}
pub struct Token {
color: TokenColor,
size: Size,
}
fn update_token(token: Token) -> Token {
let mut token = token;
token.color = TokenColor::GREEN;
token.size = Size::BIG;
token
}
fn main() {
let t1 = Token {
color: TokenColor::RED,
size: Size::BIG,
};
let changed_token = update_token(t1, TokenColor::GREEN);
println!("t1: {:?}, changed_token: {:?}", t1, changed_token);
}
這將會是所有最早必須加入的 trait. 連print都不print不出來就很難繼續開發.
有三個place holder 可以用來print出來,
{}
{:#?}
{:#?}
{:?} /{:#?}
放在string裡頭會呼喚Debug
trait, 這個trait會將所有的field print出來. 但是後者是pretty print, 對於較大得struct會比較好看.{}
放在string裡頭會呼喚Display
trait, 這個trait會將所有的field print出來.
fn main(){
let my_number = {
let second_number = 8;
second_number + 2
};
// println!("my_number: {}", my_number); <====這個會噴錯
println!("my_number: {:?}", my_number); //<=== 這個怎可以正確print出我們想要得結果
}
編按:
Debug 出來的information並不會因為release或一般build code有所差異. 我覺得debug是相對於display, 允許更多樣var type被印出來
#[derive(Debug)]
pub struct Token {
color: TokenColor,
size: Size,
}
對於我們要處理的struct, 我們可以使用#[derive(Debug)]
來讓他自動實現Debug trait. 這樣我們就可以直接print出來了. 嗎?其實
這樣還不夠因為.所有子filed都要是可以debug的struct. 這樣就會有一個問題, 我們的TokenColor和Size都需要冠上Debug trait. 這樣就會變成這樣
#[derive(Debug)]
pub enum TokenColor {
RED ,
GREEN
}
#[derive(Debug)]
pub enum Size {
BIG,
MID
}
#[derive(Debug)]
pub struct Token {
color: TokenColor,
size: Size,
}
fn update_token(mut token:Token, render_color: TokenColor) -> Token {
token.color = render_color;
return token
}
fn main() {
let t1 = Token {
color: TokenColor::RED,
size: Size::BIG,
};
let changed_token = update_token(t1, TokenColor::GREEN);
println!("t1: {:?}, changed_token: {:?}", t1, changed_token);
}
之前ownership段落有談論關於 clone 和 copy 的不同. 那我們實際上來看一下我們Why need this trait?
由於ownership的規定. 一個變數進出block (通常是function) 所需的身分經常會有變化. 最暴力無腦的解法就是傳出去的object是複製品.
一但我們對 Token, TokenColor, Size 加上copy trait後, 我們就可以直接傳出去了.
#[derive(Debug,Copy)]
pub enum TokenColor {
RED ,
GREEN
}
#[derive(Debug,Copy)]
pub enum Size {
BIG,
MID
}
#[derive(Debug,Copy)]
pub struct Token {
color: TokenColor,
size: Size,
}
fn update_token(mut token:Token, render_color: TokenColor) -> Token {
token.color = render_color;
return token
}
fn main() {
let t1 = Token {
color: TokenColor::RED,
size: Size::BIG,
};
let changed_token = update_token(t1, TokenColor::GREEN);
println!("t1: {:?}, changed_token: {:?}", t1, changed_token);
}
然而並不是所有類型都可以實現Copy trait. Rust規定只有在於所有成員都實現 Copy trait的struct才可以實現Copy trait.
那就會有一個問題 => 什麼是不能實現Copy trait的struct?
Copy 其實是作用於stack上面的複製. 舉凡Box, Vec, Heap上面的資料都不能實現Copy trait. 因為他們的資料是存在heap上面的.
更直觀地說只要是長度不是在compile time就能確定的資料都不能實現Copy trait.
Clone 則是複製值再創造新物件.
有一個很有趣的地方就是. 如果使用了Copy就必須也必須implement Clone. Copy 比較像是給compiler一個選擇, 他可以利用memcpy來複製值. 或著依循原來clone.
不過我們可以有一個大致上的理解, clone是有機會
成本比較高的.
我們嘗試實現一個函數來更新Token的狀態, 一個隨意自定義的data structure
假設我們更進一步想要開發功能對於兩個Token作相似比較.如何實現?
最暴力的就是自己幹一個.
impl eq for Token {
fn eq(&self, other: &Token) -> bool {
self.color == other.color && self.size == other.size
}
}
接下來就準備迎接錯誤. 因為 color 與 size 都是enum, 這兩個enum都沒有實作eq
trait. 所以我們必須要實作這兩個enum的eq
trait.
還是讓 PartialEq 和 Eq 來幫忙.
先謹記第一條rule實作Eq 必須實作 PartialEq.
這兩個trait的差別在於處理float的變數中自反性的問題.
舉一個比較誇張的例子. 一個資料型態我們稱為 奇怪數字
, 如同i32之類的type
他裡頭有 {1,2,3,4,'a'} 這五個東西. 我們可以實作1,2,3,4 任意兩者之間的eq, 但是我們無法實作'a'與1,2,3,4的eq. 因為'a'不是數字.
理論上一個 == 必須能個處理奇怪數字
裡頭的所有個別元素之間的比較. 但是實際上只能做到 partial.
回到float. 因為 float的集合包含了 NAN. 可以寫出以下的程式碼.
let fnan:f32 = std::f32::NAN;
那 fnan == fnan 該是 true/false? 答案是無法比較. 這也就是float只有實作PartialEq.
但實務上. 只要無腦都放然後再等compiler告訴我們該怎麼拿掉就好了.
以照這個 我們可以擴充我們的Token的eq trait.
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub enum TokenColor {
RED ,
GREEN
}
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub enum Size {
BIG,
MID
}
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub struct Token {
color: TokenColor,
size: Size,
}
fn main() {
let t1 = Token {
color: TokenColor::RED,
size: Size::BIG,
};
let t2 = Token {
color: TokenColor::GREEN,
size: Size::BIG,
};
println!("Do they equal? {}", t1 == t2);
}
最常的使用時機是需要跟 hashmap (python裡頭得dict) 一起使用.當我們想把struct作為key的時候. 我們必須要實作hash trait.
舉個例子來說.
我們想實作一個Counter 為所有大小和各色的棋子記錄數量. 比較不直觀的方法是定義好一個fix 長度的arrary
let mut counter = [[0;3]; 2];
但是在下一個承接的人,就必許時時刻刻注意這個array x,y的index對應到的是什麼. 這樣的code很容易出錯.
另外一個思路就是直接將Token作為key.
use std::collections::HashMap;
#[derive(Debug,Clone,Copy,PartialEq,Eq, Hash)]
pub enum TokenColor {
RED ,
GREEN
}
#[derive(Debug,Clone,Copy,PartialEq,Eq, Hash)]
pub enum Size {
BIG,
MID
}
#[derive(Debug,Clone,Copy,PartialEq,Eq, Hash)]
pub struct Token {
color: TokenColor,
size: Size,
}
fn main() {
let counter = HashMap::from([
(Token{color: TokenColor::RED, size: Size::BIG}, 0),
(Token{color: TokenColor::RED, size: Size::MID}, 0),
(Token{color: TokenColor::GREEN, size: Size::BIG}, 0),
(Token{color: TokenColor::GREEN, size: Size::MID}, 0),
]);
let t1 = Token {
color: TokenColor::RED,
size: Size::BIG,
};
println!("{:?}", counter[&t1]);
}