2020 Rust 特征 (Trait)

rust.jpeg

選擇 rust 的理由

  • Rust 有助于您提供代碼質量
    • 讓我們更加明確地了解性能成本
    • 便于開發人員權衡代碼性能利弊
  • Rust 更加關注代碼的質量和正確性
    • 強調內存安全,除非指定了"unsafe"
    • 強大的類型系統、匹配系統
  • 寫 Rust 有一種寫 kotlin 或者 go 這些高級語言的感覺

特征

組成我們應用通常是兩個部分數據行為,我們編程多半工作就是用行為操作數據。

fn make_true(input:&str) -> String{
    format!("{}!!",input)
}

定義函數 make_true 對字符串進行操作,然后返回字符串,這是對數據的操作。

#[derive(Debug)]
struct Fact {
    text: String
}

fn make_true(input:&Fact) -> Fact{
    Fact{text:format!("{}!!",input.text)}
}

?往往將字符串定義在范圍,我們將字符串作為對象 Fact 的屬性,然后 make_true 接收 Fact 對象然后對其 text 屬性進行操作。

我們希望方法與數據有一定關系,也就是方法屬于數據,或者說想要將方法添加到數據上,在其他語言是 method。在 rust 為結構體添加行為很簡單,

  • 通過關鍵字 impl 其后添加要添加方法到結構體名稱
  • 然后寫實現
impl Fact {
    fn make_true(&self) -> Fact{
        Fact{text:format!("{}!!",self.text)}
    }
}


fn main(){
   
    let fact = Fact{text:String::from("hello")};
    println!("{:#?}",fact.make_true());
}

上面代碼大家疑問最多可能就是 self,為什么我們需要 self

  • 通過 self 方法可以訪問到數據
  • 所以結構體內實現方法,都會得到數據引用作為方法第一個參數,然后來通過 self 引用來訪問數據,類似 javascript 和 java 中的 this。
    根據需要可以指定不同 self
  • &self 借用、只讀版本
  • &mut self 可變借用版本
  • [mut] self: 所有版本,可以操作修改 self
impl Fact {
    fn make_true(&mut self){
        self.text.push_str("!!");
    }
}
impl Fact {
    fn make_true(mut self) -> Fact{
        self.text.push_str("!!");
        self
    }
}

let fact = Fact{text:String::from("hello")};
let fact_1 = fact.make_true();
println!("{:#?}",fact_1.text);

特征定義

trait 用于定義與其他類型共享功能,這是一種抽象,類似于其他語言(例如 go 語言)中的接口。今天將介紹如何創建 trait 以及其實現和使用,最后會給出基于 trait 實現日志系統。

定義 trait

抽象方式定義共享的行為


pub trait GetInformation {
    fn get_title(&self)-> &String;
    fn get_course(&self)-> u32;
}

使用 trait 關鍵字來定義特征,然后在其中定義一系列行為,也就是空方法,需要結構體去實現。

pub struct Tut {
    pub title: String,
    pub course: u32,
}

impl GetInformation for Tut {
    fn get_title(&self) -> &String{
        &self.title
    }
    // u32 天然具有 copy 特征
    fn get_course(&self) -> u32{
        self.course
    }
}

定義結構體實現特征 GetInformation 的 get_title 這個結構體就具有 GetInfomation 特征。

fn main() {

    let js_tut = Tut{title:"vue".to_string(),course:10};
    println!("js_tut title = {} course = {}",js_tut.get_title(),js_tut.get_course())
}

在 go 語言我們可以根據行為進行劃分類別,只要具有行為結構體就屬于某一個按類別進行劃分的類別。有時候我們傳入結構體,需要具有一定能力。

實現 trait

作為參數輸入結構體需要具有一定行為,也就是結構體實現某種特征

fn print_information(tut:impl GetInformation){
    println!("title = {}",tut.get_title());
    println!("age = {}",tut.get_course())
}


fn main() {

    let js_tut = Tut{title:"vue".to_string(),course:10};
    // println!("js_tut title = {} course = {}",js_tut.get_title(),js_tut.get_course())
    print_information(js_tut);

}

默認 Trait 實現

有些時候我們可以通過給出默認方法的實現,也就是給方法提供默認行為。

trait TutInfo {
    fn get_tut_info(&self) -> String{
        String::from("supplied by zidea zone")
    }
}

定義 TutInfo 特征,然后給行為定義默認行為,如果實現特征 TutInfo 沒有復寫 get_tut_info 方法時就會默認執行 TutInfo 特征默認提供的行為。

impl TutInfo for Tut {

    // add code here
}
let js_tut_info = js_tut.get_tut_info();
println!("tut info = {}",js_tut_info);

Trait 邊界

trait 邊界指定泛型是任何擁有特定行為的類型。

fn print_information_two<T:GetInformation>(tut:T){
    println!("title = {}",tut.get_title());
    println!("age = {}",tut.get_course());
}

這里就是使用特征邊界(trait bound)來定義函數接受參數需要具有一定約束(要求結構體必須實現某種方法),所以約束就是要求結構體需要具有一定能力。

也可以指定多個特征邊界(train_bound), 來約束對象具有多個行為。其實語法還是比較好理解,一門新語言帶來很多新的特性,但是并不能改變你編程和設計程序能力。只是便于你對程序設計的實現。

trait GetTitle {
    fn get_title(&self) -> &String;
}

trait GetCourse {
    fn get_course(&self) -> u32;
}

impl GetTitle for Tut {
    fn get_title(&self)->&String{
        &self.title
    }
}

impl GetCourse for Tut {
    fn get_course(&self) -> u32{
        self.course
    }
}

fn print_information_three<T:GetTitle+GetCourse>(tut:T){
    println!("title = {}",tut.get_title());
    println!("age = {}",tut.get_course());
}

還有一種特征邊界的寫法,通過 where 對泛型進行限制,

fn print_information_five<T>(tut:T) where T:GetCourse + GetTitle{

    println!("title = {}",tut.get_title());
    println!("age = {}",tut.get_course());
}

接下來,也可以特征邊界( trait bound) 來約束函數的返回值類型。

fn get_tut() -> impl GetTitle {
    Tut{
        title:String::from("react"),
        course:20
    }
}

這里需要補充一下這里 get_tut 返回值是 trait 類型,我們不能通過條件來返回不同都實現 GetTitle 的不同結構體,這樣會報錯

let reactTut = get_tut();
println!("title of react tut = {}",reactTut.get_title());

Trait 對象

在我們所熟悉的面向對象編程語言中,對象包含數據和行為。創建對象便可調用其方法(行為)進行操作。在 rust 我們將數據保存在 enums 或是 structs ,而行為寫作 trait 里,也就是將數據和行為分開。Trait 對象行為更像傳統的對象。Trait 對象可以包含數據和行為,但是又不同于傳統對象。

  • 系統日志信息
  • 日志需要可配置
  • 在開發版提供詳細信息,而在發布版提供簡要的信息
    定義 Logger 結構體,添加輸出不同級別日志信息
#[derive(Debug)]
struct Loggger {
}

impl Logger{
    fn error(&self, message:&str){}
    fn warn(&self, message:&str){}
    fn info(&self, message:&str){}
    fn debug(&self, message:&str){}
}

上面定義了不同級別的日志輸出 error, warn, info 和 debug 級別日志輸出

根據日志輸出形式又定義不同類型日志輸出

  • FilerLogger
  • PrintLogger
  • NullLogger
  • ExternalServiceLoagger
trait Loggger {
    fn error(&self, message:&str);
    fn warn(&self, message:&str);
    fn info(&self, message:&str);
    fn debug(&self, message:&str);
}

#[derive(Debug)]
struct PrintLogger {
}

impl Loggger for PrintLogger {

    fn error(&self, message:&str){
        println!("ERROR:{} ",message)
    }
    fn warn(&self, message:&str){
        println!("ERROR:{} ",message)
    }
    fn info(&self, message:&str){
        println!("ERROR:{} ",message)
    }
    fn debug(&self, message:&str){
        println!("ERROR:{} ",message)
    }
}

?這里我們定義特征 Logger,讓不同日志輸出類型都去實現 Logger 特征,這樣他們就是不同類型卻具有相同行為的類別

pub fn log_example(logger:&Logger){
    logger.info("runing example code ..");
    logger.info("done example code");
}

這里 &Logger 也稱為Trait 對象,在 Trait 對象中包含

  • 一個指向一個基本類型(可能是 PrintLogger 也可能是 FileLogger)
  • 指針指向虛擬方法表(vtable)
    這些都是由編譯器實現的


    Trait 對象

在 Trait 對象是包含一個指向堆上數據指針,以這種形式來保存數據,當堆上值大小變化不會影響到 Trait 對象,這樣更利于分配內存給對象。

 logger.info("runing example code ..");

在上面語句編譯器會先從 vtable 中加載 info 函數地址,然調用到 info 函數的地址??凑麄€過程,可能會擔心效率,這樣做會不會影響程序的性能。

vtable
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容