簡(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.