Rust 基礎(chǔ)知識(shí)16 Trait

簡(jiǎn)介

  • Trait 類似其他語(yǔ)言中的接口土全,但是又不完全一樣捎琐,Rust 可以擴(kuò)展的事兒更多。

什么叫特征Trait

  • 可以定義抽象的共享行為
  • Trait bounds (約束)裹匙, 泛型類型參數(shù)指定為實(shí)現(xiàn)了特定行為的類型瑞凑。

如何定義

  • Trait 的定義,把方法簽名放在一起概页,來(lái)定義某種目的所必須的一組行文
1籽御、使用關(guān)鍵字 trait
2、通常情況下只有方法簽名惰匙,沒(méi)有具體實(shí)現(xiàn)技掏,或者說(shuō)無(wú)需具體實(shí)現(xiàn)。
3徽曲、trait 可以有多個(gè)方法零截,每個(gè)方法簽名占一行,以`;`結(jié)尾秃臣。
4涧衙、實(shí)現(xiàn)該trait 的類型必須提供具體的方法實(shí)現(xiàn)。
  • 舉例:
// 定義一個(gè)Trait
pub trait Summary {
    fn summarize(&self) -> String;
}

在類型上實(shí)現(xiàn)trait

  • 實(shí)現(xiàn)方法與結(jié)構(gòu)實(shí)現(xiàn)方法類似奥此。
  • 不同之處是需要添加 for 那個(gè)具體結(jié)構(gòu)弧哎,比如 : impl TName for Struct,這個(gè)設(shè)計(jì)思路和傳統(tǒng)語(yǔ)言也是有差別稚虎。
  • 舉例撤嫩,實(shí)現(xiàn)上面的 Summary

fn main() {
    
    let mut user = Tweet {
        user_name: String::from("linhai"),
        replay_count : 0,
        like_count : 0,
    };

    user.like();
    user.like();
    user.like();

    println!("value: {}, summary : {}", &user.get_like(), &user.summarize());
}

// 定義一個(gè)Trait
pub trait Summary {
    fn summarize(&self) -> String;
}
// 定義Tweet
pub struct Tweet {
    user_name :String,
    replay_count :u32,
    like_count :u32,
}
// 定義一個(gè)文章
pub struct NewsArticle {
    pub title :String,
    pub content :String,
    pub author :String,
}
// 給結(jié)構(gòu)添加普通方法
impl Tweet {
    fn like(&mut self) {
        self.like_count += 1;
    }
    fn get_like(&self) -> u32 {
        self.like_count
    }
}
// 實(shí)現(xiàn)接口到某個(gè)具體類比如 Tweet
impl Summary for Tweet {
    // 接口方法的具體實(shí)現(xiàn)
    fn summarize(&self) -> String {
        format!("{} like count :{} , replay count :{}", &self.user_name, &self.replay_count, &self.like_count)
    }
}

  • 上面的函數(shù)太長(zhǎng)了,準(zhǔn)備結(jié)構(gòu)上優(yōu)化一下蠢终,將亂七八糟的代碼挪到 lib.rs 中序攘,這時(shí)候后會(huì)發(fā)現(xiàn)一些需要修改的問(wèn)題:
1茴她、頭部加上 use hello::{Summary, Tweet}; 為什么要引入Trait ?這就是特性程奠!
2丈牢、impl Tweet 的兩個(gè)方法需要公開也就是添加pub 關(guān)鍵字,否則main就拿不到了瞄沙。
3己沛、因?yàn)樵瓉?lái)的Tweet 的結(jié)構(gòu)屬性定義的全都是私有,所以你要清楚屬性全都訪問(wèn)不到了距境,但是我并不想暴露私有屬性申尼,怎么辦呢,添加個(gè) ::create 創(chuàng)建方法吧垫桂。

  • 新建立 lib.rs 并改進(jìn)后的代碼為:

// 定義一個(gè)Trait
pub trait Summary {
    fn summarize(&self) -> String;
}
// 定義Tweet
pub struct Tweet {
    user_name :String ,
    replay_count :u32,
    like_count :u32,
}
// 定義一個(gè)文章
pub struct NewsArticle {
    pub title :String,
    pub content :String,
    pub author :String,
}
// 給結(jié)構(gòu)添加普通方法
impl Tweet {
    // 因?yàn)?user_name ,replay_count, like_count 都是私有的所以通過(guò)這個(gè)方法進(jìn)行初始化值
    pub fn create(user_name: String ) -> Tweet {
        Tweet {
            user_name  ,
            replay_count: 0 ,
            like_count: 0,
        }
    }
    pub fn like(&mut self) {
        self.like_count += 1;
    }
    pub fn get_like(&self) -> u32 {
        self.like_count
    }
}
// 實(shí)現(xiàn)接口到某個(gè)具體類比如 Tweet
impl Summary for Tweet {
    // 接口方法的具體實(shí)現(xiàn)
    fn summarize(&self) -> String {
        format!("{} like count :{} , replay count :{}", &self.user_name, &self.replay_count, &self.like_count)
    }
}

  • 此時(shí) main.rs 被精簡(jiǎn)為:
use hello::{Summary, Tweet};
fn main() {
    let mut user = Tweet::create("linhai".to_string());
    user.like();
    user.like();
    user.like();
    println!("value: {}, summary : {}", &user.get_like(), &user.summarize());
}

  • 返回結(jié)果與之前一致


    image.png
  • 通過(guò)上面的例子我們需要知道师幕,使用某個(gè)類的 trait 方法時(shí)一定要將trait 定義包含進(jìn)來(lái),否則無(wú)法使用诬滩,這是因?yàn)?Rust 的實(shí)現(xiàn)主題是trait 而非具體的結(jié)構(gòu)们衙。

實(shí)現(xiàn)Trait 的約束

  • 這個(gè)比較特殊需要注意,可以在某個(gè)類型上實(shí)現(xiàn)某個(gè)trait 的前提條件是:
  • 這個(gè)類型或這個(gè)trait 是在本地crate里定義的碱呼。
  • 就是要遵循孤兒原則,其實(shí)就是一種安全行考量宗侦。
  • 通俗的說(shuō)就是要么本地類實(shí)現(xiàn)非本地接口愚臀,要么非本地類擴(kuò)展實(shí)現(xiàn)本地接口,主要是為了防止破壞他人代碼結(jié)構(gòu)矾利,實(shí)際上不難理解姑裂。

默認(rèn)實(shí)現(xiàn)

  • 默認(rèn)實(shí)現(xiàn)是可以被重寫的,這個(gè)就很有意思了男旗,其實(shí)類似傳統(tǒng)抽象類的概念舶斧。
  • 學(xué)習(xí)了一段時(shí)間,實(shí)際上Rust 還是挺簡(jiǎn)單的察皇,從使用上規(guī)避了很多C系列語(yǔ)言的一些常見(jiàn)Bug茴厉,還是挺好的。
  • 而功能上實(shí)際上一點(diǎn)也不弱什荣,很有創(chuàng)新性的語(yǔ)言挺好的矾缓。
  • 比如接口定義可以改成:
pub trait Summary {
    // fn summarize(&self) -> String;
    fn summarize(&self) -> String {
        "... more".to_string()
    }
}
// 增加實(shí)現(xiàn)接口到某個(gè)具體類比如 NewsArticle
impl Summary for NewsArticle { }
  • 修改后測(cè)試一下:(main.rs)
use hello::{Summary, Tweet, NewsArticle};

fn main() {
    // 我們知道對(duì)于 NewsArticle 并沒(méi)有對(duì)接口實(shí)現(xiàn),但是因?yàn)榻涌谥写嬖谀J(rèn)值所以 summarize() 也是可以使用的稻爬。
    let article = NewsArticle {
        author: "linhai".to_string(),
        title: "This's a good man.".to_string(),
        content: "Very good for the earth and he learing RUST very hard.".to_string(),
    };
    println!("summary : {}", article.summarize() )
}

image.png

Trait 作為參數(shù)

  • 越來(lái)越有意思了啊嗜闻,接下來(lái)看看Trait 的一些其他用法,先看看作為參數(shù)時(shí)桅锄。
  • Trait bound 的標(biāo)準(zhǔn)用法琉雳,可以用于復(fù)雜的情況样眠,舉例:
use hello::{Summary, Tweet, NewsArticle};

fn main() {
    // 我們知道對(duì)于 NewsArticle 并沒(méi)有對(duì)接口實(shí)現(xiàn),但是因?yàn)榻涌谥写嬖谀J(rèn)值所以 summarize() 也是可以使用的翠肘。
    let article = NewsArticle {
        author: "linhai".to_string(),
        title: "This's a good man.".to_string(),
        content: "Very good for the earth and he learing RUST very hard.".to_string(),
    };
    let mut tweet = Tweet::create("linhai".to_string());
    tweet.like();
    notify_msg(article);
    notify_msg(tweet);
}
// Trait 作為參數(shù)檐束,當(dāng)做一個(gè)普通類型用就可以了,實(shí)際上和接口類似
fn notify_msg <T:Summary> (info: T) {
    println!("summary : {}", info.summarize() );
}
image.png
  • 對(duì)于上面的例子實(shí)際上也可以改成 fn notify_msg (info: impl Summary) {} 就是參數(shù)后面增加impl Trait 名稱锯茄,但是這種方式只能應(yīng)用一些簡(jiǎn)單的約束厢塘,如果約束過(guò)多參數(shù)過(guò)多會(huì)比感覺(jué)比較亂,了解一下就好肌幽。
  • 另外通過(guò)使用 + 可以指定多個(gè)Trait bound 進(jìn)行更復(fù)雜的約束晚碾,例如:
// Trait 作為參數(shù)
fn notify_msg <T:Summary + Display> (info: T) {
    println!("summary : {}", info.summarize() );
    println!("display implement info : {}", info);
}
// Trait 作為參數(shù)
fn notify_msg2 (info:impl Summary + Display) {
    println!("summary : {}", info.summarize() );
    println!("display implement info : {}", info);
}
  • 此時(shí)main.rs 無(wú)法編譯通過(guò)因?yàn)槲覀兊?code>NewsArticle和Tweet并沒(méi)有實(shí)現(xiàn)std::fmt::Display,不過(guò)有了之前的基礎(chǔ)我們很容易改進(jìn)一下我們的代碼修改lib.rs 添加如下:
// 下面的代碼沒(méi)有百度喂急,全靠代碼提示格嘁,所以VSCode的代碼提示還是要安裝好的,很有用
// 實(shí)現(xiàn)Display接口
impl Display for NewsArticle {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {}, {})", self.author, self.title, self.content)
    }
}
// 實(shí)現(xiàn)Tweet 接口
impl Display for Tweet {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {}, {})", self.user_name, self.replay_count, self.like_count)
    }
}
  • 此時(shí) cargo run 試一下吧廊移,OK 沒(méi)問(wèn)題的糕簿,實(shí)際上上面是接口用法還是很好理解的:
    image.png
  • 接下來(lái)看一下where 子句,主要是解決在泛型定義上過(guò)多的接口約束導(dǎo)致閱讀上的難以理解狡孔,我們改一版程序看看效果即可懂诗,就是把泛型定義抽象到where 子句中,就是讓無(wú)知的人類看起來(lái)更方便而已苗膝。
// Trait 作為參數(shù)
fn notify_msg <T> (info: T) 
where 
    T: Summary + Display,
{
    println!("summary : {}", info.summarize() );
    println!("display implement info : {}", info);
}

使用 Trait 作為返回類型

  • 說(shuō)實(shí)話看到這里我是有點(diǎn)費(fèi)解的殃恒,接口作為函數(shù)返回值類型在普通不過(guò)了,但是到了Rust 這里返回值如果是Trait的話那么有點(diǎn)不同辱揭。
  • impl Trait 只能返回確定的同一類型离唐,返回不同的類型即便都實(shí)現(xiàn)了該Trait 也會(huì)報(bào)錯(cuò)。
  • 仔細(xì)想了一下问窃,Rust 中實(shí)際上實(shí)現(xiàn)定義的結(jié)構(gòu)主體實(shí)際上是 Trait 而不是結(jié)構(gòu)本身亥鬓,所以在編譯的時(shí)候Rust 必須對(duì)返回類型進(jìn)行預(yù)編譯定性,這導(dǎo)致它無(wú)法像普通語(yǔ)言那樣按接口返回值域庇,因?yàn)楸旧鞹rait 不是傳統(tǒng)語(yǔ)言意義上的接口嵌戈,這里只是為了好理解這樣講罷了。
  • OK 看一下下面的例子就好了较剃,如下代碼段是非法的咕别,除非都返回Tweet,或者NewsArticle写穴,既然Rust 擁有許多成功開發(fā)的項(xiàng)目這個(gè)小缺陷惰拱,我想并不是大問(wèn)題。
// 定義一個(gè)無(wú)聊的方法
fn get_summary_class(swt : bool) -> impl Summary {
    if swt {
        Tweet::create("linhai".to_string())
    } else {
        // Tweet::create("linhai".to_string())
        // 返回NewsArticle 會(huì)報(bào)錯(cuò),即便 Summary 也做了 NewsArticle的相關(guān)實(shí)現(xiàn)偿短。
        NewsArticle {
            author: "linhai".to_string(),
            title: "This's a good man.".to_string(),
            content: "Very good for the earth and he learing RUST very hard.".to_string(),
        }
    }
}

學(xué)會(huì)了Trait 后看看之前的問(wèn)題

  • 先回顧一下之前的情況欣孤,看看能不能自己給他修復(fù)了,大致問(wèn)題代碼如下:

fn main() {
    // 定義一個(gè)整數(shù)數(shù)組序列
    let arr1 = [1,2,3,4];
    // 定義一個(gè)字符數(shù)組序列
    let arr2 = ['A','b','D','a'];
    println!("largest : {}", largest(&arr1));
    println!("largest : {}", largest(&arr2));
}

// 返回某個(gè)類型數(shù)組的最大值(這段代碼有問(wèn)題昔逗,先參考一下)
fn largest<T> (list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}
  • 直接編譯后報(bào)錯(cuò)降传,根據(jù)出錯(cuò)的地提示在方法前面對(duì)接口泛型做接口限制:<T: std::cmp::PartialOrd>
    image.png
  • 修改 fn largest 如下:
// 返回某個(gè)類型數(shù)組的最大值(這段代碼有問(wèn)題,先參考一下)
fn largest<T: std::cmp::PartialOrd> (list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

  • 然后繼續(xù)編譯勾怒,繼續(xù)出現(xiàn)錯(cuò)誤提示婆排,大致意思是說(shuō)T還需要實(shí)現(xiàn)Copy trait:
move occurs because `item` has type `T`, which does not implement the `Copy` trait
   |         help: consider removing the `&`: `item`
  • 直接修改代碼加上Copy 這個(gè)接口試試:
fn main() {
    // 定義一個(gè)整數(shù)數(shù)組序列
    let arr1 = [1,2,3,4];
    // 定義一個(gè)字符數(shù)組序列
    let arr2 = ['A','b','D','a'];
    println!("largest : {}", largest(&arr1));
    println!("largest : {}", largest(&arr2));
}

// 返回某個(gè)類型數(shù)組的最大值(這段代碼有問(wèn)題,先參考一下)
fn largest<T> (list: &[T]) -> T 
where 
    T: std::cmp::PartialOrd + Copy,
{
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}
  • 另外如果使用 Clone 接口也可以笔链,參考代碼如下:


    image.png

使用 Trait Bound 有條件的實(shí)現(xiàn)方法

  • 在使用泛型類型參數(shù)的 impl 塊上使用 Trait bound段只,我們可以有條件的為實(shí)現(xiàn)了特定Trait 的類型來(lái)實(shí)現(xiàn)方法。
  • 舉個(gè)栗子:
use std::{fmt::Display};


fn main() {
    let var1 = Point::create(1, 2);
    var1.cmp_display();
    let var2 = Point::create('林', '海');
    var2.cmp_display();
    let var3 = Point::create(Result::Ok("yes"), Result::Err("what"));
    var3.cmp_display(); // 這個(gè)編譯的時(shí)候就會(huì)出錯(cuò)
}   

// 定一個(gè)泛型結(jié)構(gòu)
struct Point <T> {
    x: T,
    y: T,
}
// 給泛型結(jié)構(gòu)加上一個(gè)pub 的create 方法
impl <T> Point<T> {
    pub fn create(x:T, y:T) -> Point<T> {
        Self {x, y}
    }
}
// 接口的約束性實(shí)現(xiàn)鉴扫,這種實(shí)現(xiàn)方式限定了使用cmp_display的類型赞枕,
// 比如i32既被Display實(shí)現(xiàn),也被PartialOrd實(shí)現(xiàn)坪创。
// 但是如果T是Result類型炕婶,這個(gè)類型就不符合Display+PartialOrd的接口約束所以也就看不到 cmp_display 這個(gè)方法
impl <T:Display+PartialOrd> Point<T> {
    pub fn cmp_display (&self){
        if self.x > self.y {
            println!("X比較大");
        }else{
            println!("Y比較大")
        }
    }
}

結(jié)束

  • 感謝閱讀,See you at work.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莱预,一起剝皮案震驚了整個(gè)濱河市柠掂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌依沮,老刑警劉巖陪踩,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異悉抵,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)摘完,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門姥饰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人孝治,你說(shuō)我怎么就攤上這事列粪。” “怎么了谈飒?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵岂座,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我杭措,道長(zhǎng)费什,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任手素,我火速辦了婚禮鸳址,結(jié)果婚禮上瘩蚪,老公的妹妹穿的比我還像新娘。我一直安慰自己稿黍,他們只是感情好疹瘦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巡球,像睡著了一般言沐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酣栈,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天险胰,我揣著相機(jī)與錄音,去河邊找鬼钉嘹。 笑死鸯乃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跋涣。 我是一名探鬼主播缨睡,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼陈辱!你這毒婦竟也來(lái)了奖年?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沛贪,失蹤者是張志新(化名)和其女友劉穎陋守,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體利赋,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡水评,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媚送。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片中燥。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖塘偎,靈堂內(nèi)的尸體忽然破棺而出疗涉,到底是詐尸還是另有隱情,我是刑警寧澤吟秩,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布咱扣,位于F島的核電站,受9級(jí)特大地震影響涵防,放射性物質(zhì)發(fā)生泄漏闹伪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祭往。 院中可真熱鬧伦意,春花似錦、人聲如沸硼补。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)已骇。三九已至离钝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間褪储,已是汗流浹背卵渴。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲤竹,地道東北人浪读。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像辛藻,于是被迫代替她去往敵國(guó)和親碘橘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容