昨天我们学了泛型generic
今天我们来学trait
traits
:定义共享行为前面多次提到的trait
,今天终于见上一面了。
trait
定义了某个特定类型具有的功能,并且还能被其他类型分享。
有点拗口,实际上就是这个特定类型里的某一个方法,而这个方法还能被用于分享使用,那就是一个trait
。
这么看,和其它语言中interface
也就是接口挺像的。
trait
一个类型的行为(behavior
)是由这个类型所拥有的method
组成的。通俗的说就是,你在大众的眼里是怎么样的取决于你所做的事情(你干嘛~哎呦)
我们先来看下如何定义
关键字是trait
,用法和struct
定义method
很像。
我们定义了一个Summary
的trait
,以及它里面summarize
的method
。
trait
也是可以pub
出去的,这样可以被别的crate
使用。
然后让我们来实现这个trait
implement
)一个trait
直接来看下例子
我们定义了一个新闻文章NewsArticle
的struct
,有标题、地址、作者以及内容四个属性。
然后我们给这个NewsArticle
的struct
实现了Summary
这个trait
,重新定义了summarize
这个方法。
同样的,我们又声明了一个Tweet
的struct
,有用户名、内容、回复以及转发四个属性。
也给它实现了Summary
这个trait
,但是重写的summarize
内容不同了。
可以看出关键字impl
和for
,implement
这个Summary
的trait
,是for
这个NewArticle
和Tweet
。
现在我们来使用下。
成功调用,注意这里的trait
如果是在别的文件里的,那么你也得导入到当前的文件中。我们来迁移下代码到aggregator.rs
这个文件(trate
)中。
然后在main.rs
里调用
但是报错了
当前scope
里NewsArticle
里并没有这个method
,但是我们确实是有在NewsArticle
里实现。
这个时候rust
需要我们把Summary
这个trait
也导入进来。
这样就不会报错了。
为啥要有这样的限制(只有导入到当前的crate
才能被implement
)呢?
rust
开发者管这叫做coherence
,翻译过来叫一致性。更贴近的描述是:孤儿规则(orphan rule
)。
其实叫做Island
孤岛会不会好一点?前端某个框架里就有这种概念。
套在我们的代码里,简单地说就是我们当前crate
里的代码不会影响到别的crate
里的代码,反过来也一样。
为什么要这么做呢?
因为可能存在多套代码为同一个类型实现同一个trait
,但是里面重写的method
内容不一样,这样rust
压根不知道哪个method
是你想调用的。
好了,回到我们的代码中。你应该会觉得这段代码有点搞笑,为什么呢?
因为和struct
完全没有区别,都是得去到对应的类型下面自己实现summarize
这个方法,所以有没有implement
Summary
这个trait
没差。
实际上真的是这样吗? 我们往下看。
implementation
额,就是不重写trait
里的method
,直接沿用。我们来改下我们的NewsArticle
这个struct
我们没有重写summarize
方法。
不过报错了
为什么会这样呢?看下我们的summarize
方法就知道了
原来我们压根没有去实现,所以也就没有这方法。我们改下
这样就没问题了。
是的,这才是trait
的作用:share
并且允许override
。
有一点需要注意,那就是如果method
并没有default implement
,那就意味着这个method
是必须的(required
),也就是说,如果你impl
了某个trait
,这个trait
有些方法没有default implement
,那就必须在对应的struct
的impl for
的block
里实现。
trait
作为参数还记得昨天使用泛型时,largest
方法有一个泛型无法比较的错误吗?那个时候我们就是引入了std::cmp::PartialOrd
这个trait
,然后赋值给T
就没问题了。实际上这也是一种implement
。
为泛型T
实现这个trait
,所以T
这个类型就可以用这个trait
的比较大小的方法了。
同上,为函数的参数实现trait
也是可以的,我们来看个例子
我们定义了一个名为notify
的function
,它的参数item
实现了Summary
这个trait
,所以可以使用summarize
这个method
。
关键字就是impl
。
当然,和泛型中那个只有i32
和char
能比较的例子一样,有些参数的类型是不能去implement
这个trait
的,会直接卡在编译的阶段。
为什么呢?
因为这些参数对应的传进来的变量是必须要真的实现了Summary
这个trait
的,也就是只有Tweet
和NewsArticle
这两个struct
的实例可以作为参数传入notify
这个方法中。
所以上面我说的参数implement
trait
就有些误解了,准确的说应该是类型限制(bound
)。
trait
约束(bound
)语法上面item:&impl Summary
的写法实际上是下面这种写法的语法糖。
把T
省略了。可以简单的记1 * x == x
,把T
当做生活中的乘法的1
。
不过反过来,这个语法糖有时候不如原始的写法,比如:
存在多个参数,这个时候每个都去写一次impl Summary
就很麻烦。而原始的写法就会比较简洁
所以语法糖只能用于简单的场景。
语法:fn fn_name(param: T) -> return type
。
multiple
)trait
约束(bound
)有可能一个trait
并不能满足你的要求,所以你可能会implement
多个trait
,那么你的参数类型自然就得同步约束才不容易出错。
我们来看下例子
用+
符号,相当于&&
,表示参数需要同时实现Summary
和Display
两个trait
。
where
语句来简洁写法上面+
的写法如果遇到很多个trait
就很难受,所以rust
官方又提供了一种简洁的写法,就是放到where
语句里。
我们来看下例子
这语句相当于
两相对比,自然是使用where
语句更优雅简洁些。
实际上我们并不是第一次接触,之前我们看的一些标准库里的method
的源码就有这个语句,比如unwrap
这个方法。
Err
变体的参数必须是impl
了fmt::Debug
这个trait
的才行。
implement
了trait
的类型参数都说了,那自然返回值也是跑不了的。
和参数的类似,我们来看下例子
也是impl
关键字,返回的数据类型自然是得约束为实现了Summary
这个trait
的类型。
后面我们会学到Interator
和closures
,它们用这种写法就比较多。
另外,返回的类型一定得是一样的,即使是都impl
了目标trait
,如果return
的类型不一样也是不行的。
比如:
这样是不行的,返回的数据有两种可能Tweet
或者NewsAriticle
trait
bound
有条件的实现method
我们直接来看例子
我们用了两个impl
,一个是给关联函数,一个是给cmp_display
这个method
。
new
关联函数需要用到Type
但是没必要去impl
Display
和PartialOrd
这俩trait
,毕竟只是在生成实例。
但是cmp_display
这个method
就可能不是所有Type
都能使用,所以这个时候用上trait
来bound
,不允许这个method
被乱七八糟的类型调用。
另外,我们也可以给任何的类型impl
一个trait
,比如
给impl
了Display
这个trait
的类型实现一个ToString
的trait
。
今天的内容还是有点多的,知识也是比较复杂的。
trait
的impl
主要的目的是帮助我们减少重复的代码,同时使用trait bound
使得trait
能固定对应类型(同样compiler
也能根据这个bound
确认下来类型)。
最后,如果觉得这篇文章对你有帮助的话,请务必点个赞,谢谢~
编辑于 2022-12-16 09:51・IP 属地广东