<tbody id="86a2i"></tbody>


<dd id="86a2i"></dd>
<progress id="86a2i"><track id="86a2i"></track></progress>

<dd id="86a2i"></dd>
<em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

    <dd id="86a2i"></dd>

    楔子

    前面我們提到過 trait,那么 trait 是啥呢?先來看個例子:

    #[derive(Debug)]
    struct?Point<T>?{
    ????x:?T,
    }
    
    impl<T>?Point<T>?{
    ????fn?m(&self)?{
    ????????let?var?=?self.x;
    ????}
    }
    
    fn?main()?{
    ????let?p?=?Point?{?x:?123?};
    }

    你覺得這段代碼有問題嗎?如果上一篇文章你還有印象的話,那么會很快發現是有問題的。因為方法 m 的第一個參數是引用,這就意味著方法調用完畢之后,結構體實例依舊保持有效,也意味著實例的所有成員值都保持有效。

    但在方法 m 里面,我們將成員 x 的值賦給了變量 var。如果成員 x 的類型不是可 Copy 的,也就是數據不全在棧上,還涉及到堆,那么就會轉移所有權,因為 Rust 默認不會拷貝堆數據。所以調用完方法 m 之后,成員?x?的值不再有效,進而使得結構體不再有效。

    所以 Rust 為了避免這一點,在賦值的時候強制要求 self.x 的類型必須是可 Copy 的,但泛型 T 可以代表任意類型,它不滿足這一特性?;蛘哒f T 最終代表的類型是不是可 Copy 的,Rust 是不知道的,所以 Rust 干脆認為它不是可 Copy 的。

    那么問題來了,雖然 T 可以代表任意類型,但如果我們賦的值決定了 T 代表的類型一定是可 Copy 的,那么可不可以告訴 Rust,讓編譯器按照可 Copy 的類型來處理呢?答案是可以的,而實現這一功能的機制就叫做 trait。

    什么是 trait

    trait 類似于 Go 里面的接口,相當于告訴編譯器,某種類型具有哪些可以與其它類型共享的功能。

    #[derive(Debug)]
    struct?Girl?{
    ????name:?String,
    ????age:?i32
    }
    
    //?trait?類似?Go?里面的接口
    //?然后里面可以定義一系列的方法
    //?這里我們創建了一個名為?Summary?的?trait
    //?并在內部定義了一個?summary?方法
    trait?Summary?{
    ????//?trait?里面的方法只需要寫聲明即可
    ????fn?summary(&self)?->?String;
    }
    
    //?Go?里面只要實現了接口里面的方法,便實現了該接口
    //?但是在?Rust?里面必須顯式地指明實現了哪一個?trait
    //?impl?Summary?for?Girl?表示為類型?Girl?實現?Summary?這個?trait
    impl?Summary?for?Girl?{
    ????fn?summary(&self)?->?String?{
    ????????//?format!?宏用于拼接字符串,它的語法和?println!?一樣
    ????????//?并且這兩個宏都不會獲取參數的所有權
    ????????//?比如這里的?self.name,format!?拿到的只是引用
    ????????format!("name:?{},?age:?{}",?self.name,?self.age)
    ????}
    }
    fn?main()?{
    ????let?g?=?Girl{name:?String::from("satori"),?age:?16};
    ????println!("{}",?g.summary());??//?name:?satori,?age:?16
    }

    所以?trait 里面的方法只需要寫上聲明即可,實現交給具體的結構體來做。當然啦,trait 里面的方法也是可以有默認實現的。

    #[derive(Debug)]
    struct?Girl?{
    ????name:?String,
    ????age:?i32
    }
    
    trait?Summary?{
    ????//?我們給方法指定了具體實現
    ????fn?summary(&self)?->?String?{
    ????????String::from("hello")
    ????}
    }
    
    impl?Summary?for?Girl?{
    ????//?如果要為類型實現?trait,那么要實現?trait?里面所有的方法
    ????//?這一點和?Go?的接口是相似的,但?Go?里面實現接口是隱式的
    ????//?只要你實現了某個接口所有的方法,那么默認就實現了該接口
    ????//?但在?Rust?里面,必須要顯式地指定實現了哪個?trait
    ????//?同時還要實現該?trait?里的所有方法
    
    ????//?但?Rust?的?trait?有一點特殊,Go?接口里面的方法只能是定義
    ????//?而?trait?里面除了定義之外,也可以有具體的實現
    ????//?如果?trait?內部已經實現了,那么這里就可以不用實現
    ????//?不實現的話則用?trait?的默認實現,實現了則調用我們實現的
    
    ????//?因此這里不需要定義任何的方法,它依舊實現了?Summary?這個?trait
    ????//?只是我們仍然要通過?impl?Summary?for?Girl?顯式地告訴?Rust
    ????//?如果只寫?impl?Girl,那么?Rust?則不認為我們實現了該?trait
    }
    fn?main()?{
    ????let?g?=?Girl{name:?String::from("satori"),?age:?16};
    ????//?雖然沒有?summary?方法,但因為實現了?Summary?這個?trait
    ????//?而?trait?內部有?summary?的具體實現,所以不會報錯
    ????//?但如果?trait?里面的方法只有聲明沒有實現,那么就必須要我們手動實現了
    ????println!("{}",?g.summary());??//?hello
    }

    總結一下就是 trait 里面可以有很多的方法,這個方法可以只有聲明,也可以同時包含實現。如果要為類型實現某個 trait,那么要通過?impl xxx for?進行指定,并且實現該 trait 內部定義的所有方法。但如果 trait 的某個方法已經包含了具體實現,那么我們也可以不實現,會使用 trait 的默認實現。

    trait 作為參數

    到目前為止,我們并沒有看到 trait 的實際用途,但相信你也能猜出來它是做什么的。假設有一個函數,只要是實現了 info 方法的結構體實例,都可以作為參數傳遞進去,這時候應該怎么做呢?

    struct?Girl?{
    ????name:?String,
    ????age:?i32,
    }
    
    struct?Boy?{
    ????name:?String,
    ????age:?i32,
    ????salary:?u32,
    }
    
    trait?People?{
    ????fn?info(&self)?->?String;
    }
    
    //?為?Girl?和?Boy?實現?People 這個 trait
    impl?People?for?Girl?{
    ????fn?info(&self)?->?String?{
    ????????format!("{}?{}",?&self.name,?self.age)
    ????}
    }
    impl?People?for?Boy?{
    ????fn?info(&self)?->?String?{
    ????????format!("{}?{}?{}",?&self.name,?self.age,?self.salary)
    ????}
    }
    
    //?定義一個函數,注意參數?p?的類型
    //?如果是?p:?xxx,則表示參數?p?的類型為?xxx
    //?如果是?p:?impl?xxx,則表示參數?p?的類型任意,只要實現了xxx這個trait即可
    fn?get_info(p:?impl?People)?->?String?{
    ????p.info()
    }
    
    fn?main()?{
    ????let?g?=?Girl?{
    ????????name:?String::from("satori"),
    ????????age:?16,
    ????};
    ????let?b?=?Boy?{
    ????????name:?String::from("可憐的我"),
    ????????age:?26,
    ????????salary:?3000,
    ????};
    ????//?只要實現了?People?這個?trait
    ????//?那么實例都可以作為參數傳遞給?get_info
    ????println!("{}",?get_info(g));?//?satori?16
    ????println!("{}",?get_info(b));?//?可憐的我?26?3000
    }

    然后以 trait 作為參數的時候,還有另外一種寫法:

    //?如果是?<T>?的話,那么?T?表示泛型,可以代表任意類型
    //?但這里是?<T:?People>,那么就不能表示任意類型了
    //?它表示的應該是實現了?People?這個?trait?的任意類型
    fn?get_info<T:?People>(p:?T)?->?String?{
    ????p.info()
    }

    以上兩種寫法是等價的,但是第二種寫法在參數比較多的時候,可以簡化長度。

    fn?get_info<T:?People>(p1:?T,?p2:?T)?->?String?{
    
    }
    //?否則話要這么寫
    fn?get_info(p1:?impl?People,?p2:?impl?People)?->?String?{
    
    }

    當然啦,一個類型并不僅僅可以實現一個 trait,而是可以實現任意多個 trait。

    struct?Girl?{
    ????name:?String,
    ????age:?i32,
    ????gender:?String
    }
    
    trait?People?{
    ????fn?info(&self)?->?String;
    }
    
    trait?Female?{
    ????fn?info(&self)?->?String;
    }
    
    //?不同的?trait?內部可以有相同的方法
    impl?People?for?Girl?{
    ????fn?info(&self)?->?String?{
    ????????format!("{}?{}",?&self.name,?self.age)
    ????}
    }
    
    impl?Female?for?Girl?{
    ????fn?info(&self)?->?String?{
    ????????format!("{}?{}?{}",?&self.name,?self.age,?self.gender)
    ????}
    }
    
    //?這里在?impl?People?前面加上了一個?&
    //?表示調用的時候傳遞的是引用
    fn?get_info1(p:?&impl?People)?{
    ????println!("{}",?p.info())
    }
    
    fn?get_info2<T:?Female>(f:?&T)?{
    ????println!("{}",?f.info())
    }
    
    fn?main()?{
    ????let?g?=?Girl?{
    ????????name:?String::from("satori"),
    ????????age:?16,
    ????????gender:?String::from("female")
    ????};
    ????get_info1(&g);??//?satori?16
    ????get_info2(&g);??//?satori?16?female
    }

    不同 trait 內部的方法可以相同也可以不同,而?Girl?同時實現了?People 和?Female 兩個 trait,所以它可以傳遞給 get_info1,也可以傳遞給 get_info2。然后為 trait 實現了哪個方法,就調用哪個方法,所以兩者的打印結果不一樣。

    那么問題來了,如果我在定義函數的時候,要求某個參數同時實現以上兩個 trait,該怎么做呢?

    //?我們只需要使用?+?即可
    //?表示參數?p?的類型必須同時實現?People?和?Female?兩個?trait
    fn?get_info1(p:?impl?People?+?Female)?{
    ????//?但由于?Poeple?和?Female?里面都有?info?方法
    ????//?此時就不能使用?p.info()?了,這樣?Rust?不知道該使用哪一個
    ????//?應該采用下面這種做法,此時需要手動將引用傳過去
    ????People::info(&p);
    ????Female::info(&p);
    }
    
    //?如果想接收引用的話,那么需要這么聲明
    //?因為優先級的原因,需要將?impl?People?+?Female?整體括起來
    fn?get_info2(p:?&(impl?People?+?Female))?{}
    
    //?或者使用類型泛型的寫法
    fn?get_info3<T:?People?+?Female>(p:?T)?{}

    最后還有一個更加優雅的寫法:

    //?顯然這種聲明方式要更加優雅,如果沒有?where?的話
    //?那么這個?T?就是可以代表任意類型的泛型
    //?但這里出現了?where
    //?因此?T?就表示實現了?People?和?Female?兩個?trait?的任意類型?
    fn?get_info<T>(p:?T)
    where
    ????T:?People?+?Female
    {
    }

    如果要聲明多個實現 trait 的類型,那么使用逗號分隔。

    fn?get_info<T,?W>(p1:?T,?p2:?W)
    where
    ????T:?People?+?Female,
    ????W:?People?+?Female
    {
    }

    可以看出,Rust 的語法表達能力還是挺豐富的。

    trait 作為返回值

    trait 也是可以作為返回值的。

    struct?Girl?{
    ????name:?String,
    ????age:?i32,
    ????gender:?String,
    }
    
    trait?People?{
    ????fn?info(&self)?->?String;
    }
    
    impl?People?for?Girl?{
    ????fn?info(&self)?->?String?{
    ????????format!("{}?{}",?&self.name,?self.age)
    ????}
    }
    
    fn?init()?->?impl?People?{
    ????Girl?{
    ????????name:?String::from("satori"),
    ????????age:?16,
    ????????gender:?String::from("female"),
    ????}
    }
    
    fn?main()?{
    ????let?g?=?init();
    ????println!("{}",?g.info());??//?satori?16
    }

    一個 trait 可以有很多種類型實現,返回任意一個都是可以的。

    實現一個 max 函數

    這里我們定義一個函數 max,返回數組里面的最大元素,這里先假定數組是 i32 類型。

    //?arr?接收一個數組,我們將它聲明為?&[i32]
    //?這個聲明比較特殊,我們舉幾個例子解釋一下
    //?arr:?[i32;5],表示接收類型為?i32?長度為?5?的靜態數組
    //?arr:?Vec<f64>,表示接收類型為?f64?的動態數組,長度不限
    /*?arr:?&[i32],表示接收?i32?類型數組的引用
    ???并且數組可以是動態數組,也可以是靜態數組,長度不限
    ???對于當前求最大值來說,我們不應該關注數組是靜態的還是動態的
    ???所以應該聲明為?&[i32],表示都支持
    */
    fn?max(arr:?&[i32])?->?i32{
    ????if?arr.len()?==?0?{
    ????????panic!("數組為空")
    ????}
    ????//?獲取數組的第一個元素,然后和后續元素依次比較
    ????let?mut?largest?=?arr[0];
    ????for?&item?in?arr?{
    ????????if?largest?<?item?{
    ????????????largest?=?item
    ????????}
    ????}
    ????largest
    }
    
    fn?main()?{
    ????let?largest?=?max(&vec![1,?23,?13,?4,?15]);
    ????println!("{}",?largest);??//?23
    }

    還是很簡單的,但問題來了,如果我希望它除了支持整型數組外,還支持浮點型該怎么辦呢?難道再定義一個函數嗎?顯然這是不現實的,于是我們可以考慮泛型。

    fn?max<T>(arr:?&[T])?->?T?{
    ????if?arr.len()?==?0?{
    ????????panic!("數組為空")
    ????}
    ????let?mut?largest?=?arr[0];
    ????for?&item?in?arr?{
    ????????if?largest?<?item?{
    ????????????largest?=?item
    ????????}
    ????}
    ????largest
    }

    使用泛型的話,代碼就是上面這個樣子,你覺得代碼有問題嗎?

    不用想,問題大了去了。首先函數接收的是數組的引用,那么函數調用結束后,數組依舊保持有效,那么數組里面的元素顯然也是有效的。但在給 largest 賦值的時候,等號右邊是 arr[0]。如果數組里面的元素不是可 Copy 的,那么就會失去所有權,因為 Rust 不會拷貝堆數據,那這樣的話數組之后就不能用了。所以這種情況 Rust 要求元素是可 Copy 的,但實際情況是不是呢?Rust 是不知道的,所以會報錯,認為不是可 Copy 的,這是第一個錯誤。

    然后是 for &item in arr,這段代碼的錯誤和上面相同,在遍歷的時候會依次將元素拷貝一份賦值給 item。但要求拷貝之后彼此互不影響,這就意味著數據必須全部在棧上。但 T 代表啥類型,該類型的數據是否全部在棧上 Rust 是不知道的,于是報錯。

    第三個錯誤就是 largest < item,因為這涉及到了比較,但 T 類型的數據能否比較呢?Rust 也是不知道的,所以報錯。

    因此基于以上原因,如果想讓上述代碼成立,那么必須對 T 進行一個限制。

    fn?max<T>(arr:?&[T])?->?T
    where
    ????//?相當于告訴?Rust
    ????//?這個?T?是可比較的、可?Copy?的
    ????//?或者說?T?實現了?PartialOrd?和?Copy?這兩個?trait
    ????T:?PartialOrd?+?Copy,
    {
    ????if?arr.len()?==?0?{
    ????????panic!("數組為空")
    ????}
    ????let?mut?largest?=?arr[0];
    ????for?&item?in?arr?{
    ????????if?largest?<?item?{
    ????????????largest?=?item
    ????????}
    ????}
    ????largest
    }
    
    fn?main()?{
    ????let?largest?=?max(&vec![1,?23,?13,?4,?15]);
    ????println!("{}",?largest);?//?23
    ????let?largest?=?max(&vec![1.1,?23.1,?13.1,?4.1,?15.1]);
    ????println!("{}",?largest);?//?23.1
    }

    以上我們就實現了數組求最大值的邏輯,通過對 T 進行限制,告訴 Rust 泛型 T 代表的類型實現了 PartialOrd 和 Copy 這兩個 trait。然后當我們調用的時候,Rust 就會檢測類型是否合法:

    深入了解Rust中trait的使用

    顯然當元素類型為 String 的時候就會報錯,因為 Rust 檢測到該類型沒有實現 Copy 這個 trait。

    那如果我希望,max 函數也支持 String 類型的數組呢?

    fn?max<T>(arr:?&[T])?->?&T
    where
    ????//?T?可以不實現?Copy?trait
    ????//?但必須實現?PartialOrd
    ????T:?PartialOrd,
    {
    ????if?arr.len()?==?0?{
    ????????panic!("數組為空")
    ????}
    ????//?這里必須要拿到引用,可能有人覺得調用?clone?可不可以
    ????//?答案是不可以,因為這個函數不僅支持?String
    ????//?還要支持整型、浮點型,所以只能獲取引用
    ????let?mut?largest?=?&arr[0];
    ????//?因為?arr?是個引用,所以遍歷出來的?item?也是元素的引用
    ????for?item?in?arr?{
    ????????//?雖然這里表面上比較的是引用,但其實比較的是值
    ????????//?比如?let?(a,?b)?=?(11,?22)
    ????????//?那么?a?<?b?和?&a?<?&b?的結果是一樣的
    ????????if?largest?<?item?{
    ????????????largest?=?item
    ????????}
    ????}
    ????largest
    }
    
    fn?main()?{
    ????let?arr?=?&vec![String::from("A"),?String::from("Z")];
    ????println!("{}",?max(arr));?//?Z
    
    ????let?arr?=?&vec![1,?22,?11,?34,?19];
    ????println!("{}",?max(arr));?//?34
    
    ????let?arr?=?&vec![1.1,?22.1,?11.2,?34.3,?19.8];
    ????println!("{}",?max(arr));?//?34.3
    }

    此時我們就實現了基礎類型的比較,還是需要好好理解一下的。

    原文地址:https://mp.weixin.qq.com/s/6KMtibY1FDClt329kWP6bA

    相關文章:

    免费一级a片在线播放视频|亚洲娇小性XXXX色|曰本无码毛片道毛片视频清|亚洲一级a片视频免费观看
    <tbody id="86a2i"></tbody>

    
    
    <dd id="86a2i"></dd>
    <progress id="86a2i"><track id="86a2i"></track></progress>

    <dd id="86a2i"></dd>
    <em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

      <dd id="86a2i"></dd>