坏蛋Dan
知乎@坏蛋Dan
发布时间:2024.1.4

前言

昨天我们学了泛型generic

坏蛋Dan:rust基础学习--day21

今天我们来学trait


traits:定义共享行为

前面多次提到的trait,今天终于见上一面了。

trait定义了某个特定类型具有的功能,并且还能被其他类型分享。

有点拗口,实际上就是这个特定类型里的某一个方法,而这个方法还能被用于分享使用,那就是一个trait

这么看,和其它语言中interface也就是接口挺像的。

定义一个trait

一个类型的行为(behavior)是由这个类型所拥有的method组成的。通俗的说就是,你在大众的眼里是怎么样的取决于你所做的事情(你干嘛~哎呦)

我们先来看下如何定义

关键字是trait,用法和struct定义method很像。

我们定义了一个Summarytrait,以及它里面summarizemethod

trait也是可以pub出去的,这样可以被别的crate使用。

然后让我们来实现这个trait


为一个类型实现(implement)一个trait

直接来看下例子

我们定义了一个新闻文章NewsArticlestruct,有标题、地址、作者以及内容四个属性。

然后我们给这个NewsArticlestruct实现了Summary这个trait,重新定义了summarize这个方法。

同样的,我们又声明了一个Tweetstruct,有用户名、内容、回复以及转发四个属性。

也给它实现了Summary这个trait,但是重写的summarize内容不同了。

可以看出关键字implforimplement这个Summarytrait,是for这个NewArticleTweet

现在我们来使用下。

成功调用,注意这里的trait如果是在别的文件里的,那么你也得导入到当前的文件中。我们来迁移下代码到aggregator.rs这个文件(trate)中。

然后在main.rs里调用

但是报错了

当前scopeNewsArticle里并没有这个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,那就必须在对应的structimpl forblock里实现。


trait作为参数

还记得昨天使用泛型时,largest方法有一个泛型无法比较的错误吗?那个时候我们就是引入了std::cmp::PartialOrd这个trait,然后赋值给T就没问题了。实际上这也是一种implement

为泛型T实现这个trait,所以T这个类型就可以用这个trait的比较大小的方法了。

同上,为函数的参数实现trait也是可以的,我们来看个例子

我们定义了一个名为notifyfunction,它的参数item实现了Summary这个trait,所以可以使用summarize这个method

关键字就是impl

当然,和泛型中那个只有i32char能比较的例子一样,有些参数的类型是不能去implement这个trait的,会直接卡在编译的阶段。

为什么呢?

因为这些参数对应的传进来的变量是必须要真的实现了Summary这个trait的,也就是只有TweetNewsArticle这两个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


多(multipletrait约束(bound)

有可能一个trait并不能满足你的要求,所以你可能会implement多个trait,那么你的参数类型自然就得同步约束才不容易出错。

我们来看下例子

+符号,相当于&&,表示参数需要同时实现SummaryDisplay两个trait


使用where语句来简洁写法

上面+的写法如果遇到很多个trait就很难受,所以rust官方又提供了一种简洁的写法,就是放到where语句里。

我们来看下例子

这语句相当于

两相对比,自然是使用where语句更优雅简洁些。

实际上我们并不是第一次接触,之前我们看的一些标准库里的method的源码就有这个语句,比如unwrap这个方法。

Err变体的参数必须是implfmt::Debug这个trait的才行。


返回一个implementtrait的类型

参数都说了,那自然返回值也是跑不了的。

和参数的类似,我们来看下例子

也是impl关键字,返回的数据类型自然是得约束为实现了Summary这个trait的类型。

后面我们会学到Interatorclosures,它们用这种写法就比较多。

另外,返回的类型一定得是一样的,即使是都impl了目标trait,如果return的类型不一样也是不行的。

比如:

这样是不行的,返回的数据有两种可能Tweet或者NewsAriticle


使用trait bound有条件的实现method

我们直接来看例子

我们用了两个impl,一个是给关联函数,一个是给cmp_display这个method

new关联函数需要用到Type但是没必要去impl DisplayPartialOrd这俩trait,毕竟只是在生成实例。

但是cmp_display这个method就可能不是所有Type都能使用,所以这个时候用上traitbound,不允许这个method被乱七八糟的类型调用。

另外,我们也可以给任何的类型impl一个trait,比如

implDisplay这个trait的类型实现一个ToStringtrait


总结

今天的内容还是有点多的,知识也是比较复杂的。

traitimpl主要的目的是帮助我们减少重复的代码,同时使用trait bound使得trait能固定对应类型(同样compiler也能根据这个bound确认下来类型)。

最后,如果觉得这篇文章对你有帮助的话,请务必点个赞,谢谢~

参考

  1. ^rust-defining-share-behavior https://doc.rust-lang.org/book/ch10-02-traits.html#traits-defining-shared-behavior
  2. ^rust-define-a-trait https://doc.rust-lang.org/book/ch10-02-traits.html#defining-a-trait
  3. ^rust-implement-a-trait-for-a-type https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type
  4. ^rust-use-default-implementation https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations
  5. ^rust-use-trait-as-parameters https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
  6. ^rust-trait-bound-syntax https://doc.rust-lang.org/book/ch10-02-traits.html#trait-bound-syntax
  7. ^rust-specify-multiple-trait-bound https://doc.rust-lang.org/book/ch10-02-traits.html#specifying-multiple-trait-bounds-with-the--syntax
  8. ^rust-clearer-trait-bound-with-where-clauses https://doc.rust-lang.org/book/ch10-02-traits.html#clearer-trait-bounds-with-where-clauses
  9. ^rust-return-types-that-implement-trait https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits
  10. ^rust-use-trait-bound-conditionally-implement-methods https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods

编辑于 2022-12-16 09:51・IP 属地广东