iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0

Derive

rust一路寫來都會給人一種非常節制且保守的風格, 能不多給功能就不多給功能.這幾個好夥伴幾乎可以說是幾乎是在寫code一開始就必須要用到的.

  • Clone, to create T from &T via a copy.
  • Copy, to give a type 'copy semantics' instead of 'move semantics'.
  • Hash, to compute a hash from &T.
  • Default, to create an empty instance of a data type.
  • Debug, to format a value using the {:?} formatter.

我們嘗試實現一個函數來更新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);
}

Debug

這將會是所有最早必須加入的 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);
}

Copy & Cloned

之前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是有機會成本比較高的.

PartialEq 與 Eq

我們嘗試實現一個函數來更新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);
}

Hash

最常的使用時機是需要跟 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]);
}

上一篇
[D9] Modeling & OO
下一篇
[D11]Generics(泛型)
系列文
大閘蟹料理指南(rust)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言