定义泛型trait
泛型它又来了,泛型和 trait 又会发生什么样的火花呢?先看下面的代码:
// 定义一个泛型trait trait MyPrint<T> { // 输出传递的参数 fn print(&self, x: T) -> T; } // 测试结构体 struct Test; // 为Test实现MyPrint impl MyPrint<i32> for Test { // 返回值 fn print(&self, x: i32) -> i32 { return x; } } fn main() { let test = Test; // 直接输出结果 println!("{}", test.print(3)); }
MyPrint 是一个泛型 trait,在 trait 名称后面添加 <T> 符号则标明该 trait 是一个泛型的 trait。我们声明了一个结构体 Test,为其实现了 MyPrint<i32> 的trait。最后,我们就可以调用 print
直接输输出 i32 类型的数据了 。像类似 MyPrint<i32> 、 MyPrint<String> 、 MyPrint<u8>等等这都不是同一类型的。
泛型约束
我们还可以使用 trait 对泛型进行约束,trait 约束与泛型参数声明在一起,这种方式一般用于复杂的开发场景。示例代码如下:
fn trait_demo<T: TraitOne + TraitTwo + TraitOther>(param: T) { // code... }
这里的 T 表示该参数同时实现了 TraitOne, TraitTwo, TraitOther 三个 trait。
如果存在多个泛型,我们也可以对多个泛型进行约束。示例如下:
fn multi_fun<T: TraitOne, E: TraitTwo + TraitOther>(param1: T, param2: E) { // code... }
在遇到更加复杂的开发场景时,可能存在泛型存在多个 trait 约束的场景,为了避免可读性差和“头重脚轻”的问题,我们可以通过 where 关键字来进行优化。可以将约束写在函数签名后面——where + 约束条件。
以 multi_fun 为例,转为 where 关键字的写法如下:
fn multi_fun_where<T,E>(param1: T, param2: E) where T: TraitOne, E: TraitTwo + TraitOther { // code... }
由于示例代码过多,这里我就不再贴代码占用空间了,详细示例代码请点击文末 阅读原文下载。
PS:如果这里有读者读过的张汉东老版本编著的《Rust编程之道》,请不要被书中的 trait 数学的集合概念所误导,该篇内容讲述的逻辑和概念是错误的。作者回应将在第二版第4次印刷修改。附原文地址:https://github.com/ZhangHanDong/tao-of-rust-codes/issues/99
0x02 Supertraits
Rust 中没有 “继承”的概念,但是我们可以定义一个 trait 为另一个 trait 的超集。在某些文章中又叫做”子 trait“ 或者”继承“。
// Supertraits trait Animal { fn speak(&self); } trait Dog: Animal { // 狗还会跳 fn jump(&self); } struct SmallDog; // 为 SmallDog 实现 Dog 的同时,也必须实现 Animal impl Animal for SmallDog { fn speak(&self) { } } impl Dog for SmallDog { fn jump(&self) { } }
我们再为 SmallDog 实现 Dog 的同时,也要为其实现 Animal。实现的顺序是无所谓的,但必须要实现,否则会产生错误。其实这点类似于 Java 中的接口继承。
0x03 trait 中的 Self
在 trait 中,我们可以使用关键字 Self 作为类型。这里的 Self 是首字母大写的。我们先看下官方代码示例。下面内容可以仅作为了解。
这是官方源码中的 Clone trait,clone 方法返回了 Self。clone 方法的返回类型就是自身类型, 这也就意味着 Self 其实就是自身类型。我们再进一步剖析它。我这里随便点了几个实现,下面源码来自 client.rs
。
我们从这两个 impl 代码块中,可以清晰的看出,其实 Self 其实就是当前类型的别名。第一个 impl 块中,Self 是 Group 的别名,在第二个 impl 块中,Self 是 Literal 的别名。
0x04 小结
本篇文章着重介绍了泛型与 trait 结合使用的场景。这也是应对复杂场景的一种解决办法,也是我们必须要掌握的重点。有关 trait 的相关知识还有很多,下一篇文章依然还是 trait。