rust中trait的使用方法详解
作者:王开源
介绍
trait用中文来讲就是特征,它就是一个标记,只不过这个标记被用在特定的地方,也就是类型参数的后面,用来限定这个类型参数可能的类型范围。trait是一种约束。
具体关系为: 变量(值空间太过宽泛,添加约束) -> 类型(约束过死,放开约束) -> 泛型(类型空间太过宽泛,添加约束) -> trait
语法上,T: TraitA意思就是对类型参数T施加TraitA这个标记,具体实现为:
trait TraitA {} struct Atype; impl TraitA for Atype {}
对于某个类型T来说,如果它实现了这个TraitA,这个类型就满足约束
fn print<T: std::fmt::Display>(p: Point<T>){...}
上面这这段代码的意思是,Display对类型参数T做了约束,要求将来要带入的具体类型必须实现Display这个trait。也就是说,trait对类型参数施加约束的同时,也对具体的类型提供了能力,在Rust中约束和能力就是一体两面,是同一个东西。
trait中包含什么
trait里面可以包含关联函数、关联类型和关联常量。
关联函数
// 下列代码涉及所有权三态 trait Sport { fn play(&self) {} // 注意这里一对花括号,就是trait的关联函数的默认实现 fn play_mut(&mut self); fn play_own(self); fn play_some() -> Self; } struct Football; impl Sport for Football { // 由于play函数在trait中有关联函数的默认实现,结构体则可以不实现此函数 fn play_mut(&mut self) {} fn play_own(self) {} fn play_some() -> Self { Self } } fn main() { let mut f = Football; f.play(); f.play_mut(); f.play_own(); let _g = Football::play_some(); let _g = <Football as Sport>::play_some(); // 等同于上一条代码 }
关联类型
在trait中,可以带一个或多个关联类型。关联类型起一个类型占位功能,定义trait时声明,在把trait实现到结构体上的时候为其指定具体的类型。
pub trait Sport { type ST; // 声明关联类型 fn play(&self, st: Self::ST); // 将关联类型应用到关联函数 } struct Football; pub enum SportType { Land, Water, } impl Sport for Football { type ST = SportType; // 为关联类型指定具体类型 fn play(&self, st: Self::ST){} // 方法中用到关联类型 } fn main() { let f = Football; f.play(SportType::Land); }
在T上使用关联类型
trait TraitA { type Mytype; } fn doit<T: TraitA>(a: T::Mytype) {} // 这里在函数中使用关联类型 struct TypeA; impl TraitA for TypeA { type Mytype = String; // 具化关联类型为String } fn main() { doit::<TypeA>("abc".to_string()); // 指定泛型T为结构体TypeA }
在约束中具化关联类型
trait TraitA { type Item; } // 意思就是限制x必须是实现TraitA而且它的关联类型Item必须是String的类型 struct Foo<T: TraitA<Item=String>> { x: T }
对关联类型的约束
在定义关联类型的时候,也可以给关联类型添加约束。后面在具化这个类型的时候,那些类型必须要满足于这些约束
use std::fmt::Debug; trait TraitA { type Item: Debug; // 这里对关联类型添加了Debug的约束 } #[derive(Debug)] struct A; // 这里在结构体A上自动derive Debug约束 struct B; impl TraitA for B { type Item = A; // 这里类型A已经满足Debug约束 }
在使用时可以加强关联类型的约束
... fn doit<T>(a: T) where T: TriatA, // 约束T类型必须实现TraitA T::Item: Debug + PartialEq, // 同时约束trait的关联类型必须实现Debug和PartialEq { }
关联常量
和关联类型不同的是,关联常量可以在trait定义的时候指定,也可以在给具体类型实现的时候指定。
trait TraitA { const LEN: u32 = 10; } struct A; impl TraitA from A { const LEN: u32 = 12; }
where
当类型参数后面有对个trait约束的时候,会显得头重脚轻,所以Rust提供了where语法
fn doit<T: A + B + C + D + E + F>(t: T) -> i32 {} fn doit<T>(t: T) -> u32 where T: A + B + C + D + E + F {}
约束依赖
如果某种类型要实现TraitA,那么它也要同时实现TraitB。
trait TraitB {} trait TraitA: TraitB {} // 等价于 trait TraitC where Self: TraitB {}
约束之间是完全平等的,没有上下级关系
约束中同名方法的访问
trait Shape { fn play(&self) { println!("1"); } } trait Circle: Shape { fn play(&self) { println!("2"); } } struct A; impl Shape for A {} impl Circle for A {} impl A { fn play(&self) { println!("3"); } } fn main() { let a = A; a.play(); // 调用类型A上实现的play方法 <A as Circle>::play(&a); // 调用trait Circle上的play方法 <A as Shape>::play(&a); // 调用trait Shape上的play方法 }
这种语法叫做完全限定语法,是调用类型上某个方法的完整路径表达。
用trait实现能力配置
trait提供了寻找方法的范围
- 检查有没有直接在这个类型上实现这个方法
- 检查有没有在这个类型上实现某个trait,trait中有这个方法 一个类型可能实现了多个trait,不同的trait中各有一套方法,这些不同的方法中可能还会出现同名方法。Rust在这里采用了一种惰性的机制,由开发者指定在当前的mod或scope中使用哪套或哪几套能力。因此,对应地需要开发者手动地将要用到的trait引入当前scope。
mod module_a { pub trait Shape { fn play(&self) { println!("1"); } } pub struct A; impl Shape for A {} } mod module_b { use supper::module_a::Shape; // 需要同时引入A用到的trait use super::module_a::A; fn doit() { let a = A; a.play(); } }
孤儿原则
为了不导致混乱,Rust要求在一个模块中,如果要对一个类型实现某个trait,这个类型和这个trait其中必须有一个是在当前模块定义的,如果必须用的话,可以用Newtyoe模式
Blanket Implementation
统一实现后,就不要对某个具体的类型再实现一次了,因为同一个trait只能实现一次到某个类型上。这个不像对类型做impl,可以实现多次(函数名要不冲突)。
trait TraitA {} trait TraitB {} impl<T: TraitB> TraitA for T {} // 为所有被TraitB约束的类型实现TraitA impl TraitB for u32 {} // impl TraitA for u32 {} // 无法再次实现
到此这篇关于rust中trait的使用方法详解的文章就介绍到这了,更多相关rust trait内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!