• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
Asc.416e74
博客园    首页    新随笔    联系   管理     

Rust Trist

Trait

Trait 是什麼東西

Trait 中定義了一組方法,類似於其他語言當中的接口,但是不同於其他語言中的接口(後面會展開)。因爲其內部是一組方法,因此 Trait 所定義的是一種公共行爲。比如所有男生被要求上公廁的時候只能進男廁所,所有運動員被要求不允許毆打裁判……也就是讓一些不同的類型卻擁有相同的行爲(可調用方法)。

自定義 Trait

你可以通過如下的方式自定義一個 Trait,這個 Trait 中擁有一個方法簽名(只有簽名,沒有實現),他的意思是要求具備 SayHello 這個 Trait 的類型都必須擁有一個方法,這個方法名叫 say,並且這個方法返回一個 str。

trait SayHello {
    fn say(&self) -> &'static str;
}

PS:&self 參數是必要的,不然實例化後的對象無法調用。

爲類型實現 Trait

如果你希望爲自己的類型實現 SayHello 這個 Trait 你可以像這樣

struct Man {
    name: String,
}

impl SayHello for Man {
    fn say(&self) -> &'static str {
        "Hello Man"
    }
}

fn main() {
    let m: Man = Man{name: String::from("Ant")};
    println!("{}",m.say());
}

默認實現

如果你定義來一百個類型,而每一個類型都需要實現 SayHello Trait,但是每一個 say 方法返回的內容都是一模一样的,因此你可以爲 Trait 添加默認實現。

trait SayHello {
    fn say(&self) -> &'static str {
        "Hello 🌍"
    }
}

struct Man {
    name: String,
}

impl SayHello for Man {}

fn main() {
    let m: Man = Man{name: String::from("Ant")};
    println!("{}",m.say());
}

原本 Trait 當中只是添加了方法簽名,並沒添加實現,所以某一個類型需要實現某個 Trait 的時候需要實現裏面的方法。但是,當添加了默認實現之後,再爲類型實現 Trait 的時候並不需要實現裏面的方法,而且只需要直接使用即可(這時候依舊可以去手動實現,並且手動實現會復蓋原本的實現)。

使用 Trait 作爲參數

實例

Trait 既然定義了某些類型的公共行爲,除了讓類型去實現 Trait 之後主動調用之外,還可以讓第三方函數來調用這個實現,比如下面這樣。

trait SayHello {
    fn say(&self) -> &'static str {
        "Hello 🌍"
    }
}

struct Man {
    name: String,
}

impl SayHello for Man {}

fn say(object: impl SayHello) {
    println!("say \"{}\" in fn say",object.say())
}

fn main() {
    let m: Man = Man{name: String::from("Ant")};
    say(m)
}

此處把 Trait 作爲參數,用來約束形參 object 的類型,但是與其他如 i32,i64 不同的是 Trait 多了一個 iml 關鍵字,這個意思是說,object 參數是一個實現了 SayHello Trait 的實例。如果你需要以借用的方式傳遞參數,那麼 & 關鍵字需要添加在 impl 之前,: 之後。

其實只是語法糖

另外上面這種寫法其實只是一種語法糖,是完全等價與下面的這種寫法的。不難看出,與其說是使用 Trait 作爲參數,不如說是一個泛型方法更便於理解。

fn say<T: SayHello> (object: &T) {
    println!("say \"{}\" in fn say",object.say())
}

上面的語法糖也被叫做 Trait 約束(Trait Bound)。那麼什麼使用使用那一種書寫方式呢?一般來說對於比較簡單的示例會使用第一種寫法(帶有 impl 關鍵字),而對於比較複雜的情況使用完整寫法(Trait Bound 使用泛型)。如下面的例子。

fn func(item1: impl SayHello, item2: impl SayHello) {}

func 接受的兩個參數確實擁有相同的 Trait 但是並不能確定他們是不是相同的類型,如果你需要保證這一點則需要通過完整的寫法:

fn func<T: SayHello>(item1: T, item2: T) {}

而且不難看出,這時候完整的寫法看起來反而更加簡短。而且由於 Trait 可以通過 + 進行連接——一個類型可以擁有多個 Trait,函數自然也能要求某個參數同時實現了多個 Trait,因此可以想像如果通過 + 連接了多個 Trait 的時候第一種寫法並不簡短,也並不優雅,所以這時候可以通過完整寫法+ where 來讓代碼看起來更加優雅一些

where 語法

相當於把 Trait 分開指定,讓代碼在可讀性上得到提升。

fn func<T, U>(item1: T, item2: U)
where
    T: Display + SayHello,
    U: SayHello + PartialOrd,
{
    // TODO
}

返回實現了 Trait 的類型

既然 Trait 可以被當作參數類型一樣使用,那麼時候可以作爲返回值類型使用呢?結論是可以的

trait SayHello {
    fn say(&self) -> &'static str {
        "Hello 🌍"
    }
}

struct Man {}

impl SayHello for Man {}

fn new_sayer()-> impl SayHello {
    Man{}
}

fn main() {
    let m = new_sayer();
    println!("{}",m.say());
}

如此一來我們便可以在 new_sayer 當中返回任何實現了 SayHello 的類型。但是這裏有一個問題,那就是當你觀察 main 函數當中的變量 m 的數據類型的時候你會發現他並不是 Man 類型,而只是一個實現了 SayHello 的類型。關於這一點我們會在後續關於迭代器和閉包的相關內容中再次提到。

posted @ 2022-10-17 22:17  ストッキング  阅读(30)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3