关联类型
关联类型是在特征定义的语句块中,声明一个自定义类型,这样就可以在特征的方法签名中使用改类型:
pub trait Iterator{
type Item;
fn next(&mut self)->Option<Self::Item>;
}
上面是标准库中的迭代器特征iterator,有一个Item类型,用于代替遍历的值的类型
同时,next也返回了以恶个Item类型,不过使用Option枚举进行了包裹,加入迭代器中的值还是i32,那么调用next方法就将获取一个Option<i32>的值
Self用来指代当前调用者的具体类型,那么Self::Item用于指代该类型实现中定义的Item类型:
impl Iterator for Counyer{
type Item = u32;
for next(&mut self)->Option<Self::Item>{
//
}
}
fn main(){
let c = Counter{..}
c.next()
}
我们在上面为Counter类型实现了Iterator特征,变量c是特征Iterator的实例,也是next方法的调用者.对于next而言,Self是调用者c的具体方法:Counter,而Self::Item是Counter中定义的Item类型:u32
泛型:
pub trait Iterator<Item>{
fn next(&mut self)->Option<Item>;
}
使用泛型需要在所有地方都写Iterator<Item>,而是用了关联类型,只需要写Iterator,增加可读性:
pub trait Cacheable:Clone+Default+fmt::Debug+Decodable+Encodable{
type Address:AsREf[u8]+Clone+fmt::Debug+Eq+Hash;
fn is_null(&self)->bool;
}
Address的写法远比AsREf<[u8]>+Clone+fmt::Debug+Eq+Hash更简单
还有一个例子:
trait Container<A,B>{
fn contains(&self,a:A,b:B)->bool;
}
fn difference<A,B,C>(container:&C)->i32
where
C:Container<A,B>{...}
由于使用泛型,函数头部也必须增加泛型的声明,而是用关联类型,将得到可读性很多的代码:
trait Container{
type A;
type B;
fn contains(&self,a:&Self::A,b:&Self::B)->bool;
}
fn difference<C:Container>(container:&C){}
关联类型还可以被其他特征约束
trait Container{
type A:Display;
type B;
fn contains(&self,a:&Self::A,&Self::B)->bool;
}
默认使用泛型参数类型
当使用泛型类型参数时候,可以为其指定一个默认的具体类型,例如标准库中的std::ops::Add特征:
trait Add<RHS=Self>{
type Output;
fn add(self,rhs:RHS)->Self::Output;
}
有一个泛型参数RHS,但是这里给RHS一个默认值,也就是用户不指定RHS时,默认使用2个相同类型的值进行相加,然后返回1个关联类型Output
use std::ops::Add;
#[derive(Debug,PartialEq)]
struct Point{
x:i32,
y:i32
}
impl Add for Point{
type Output = Point;
fn add(self,other:Point)->Point{
Point{
x:self.x+other.x,
y:self.y+other.y,
}
}
}
fn main(){
assert_eq!(Point{x:1,y:0}+Point{x:2,y:3},
Point{x:3,y:3});
}
上面就是为Point提供了+的能力,也就是运算符重载,但是rust不支持创建自定义运算符,也无法为所有运算符进行重载,只有定义在std::ops中的运算符才能进行重载
下面来创建2个不相同的类型的相加:
use std::ops::Add
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters{
type Output = Millimeters;
fn add(self,others:Meters)->Millimeters{
Millimeters(self.0+(other.0*1000))
}
}
这时候就不能再使用默认的RHS,否则就成为了Millimeters+Millimeters的形式
默认类型主要用于:
- 减少实现的样板代码
- 拓展类型但是无需大幅修改现有的代码 如果在一个复杂类型的基础上,新引入一个泛型类型,可能需要修改很多地方,但是如果新引入的泛型参数有了默认类型,情况就会好很多
调用同名的方法
不同特征拥有同名的方法是很正常的;甚至除了特征上电同名方法外,类型上,也有同名方法
trait Pilot{
fn fly(&self);
}
trait Wizard{
fn fly(&self);
}
struct Human;
impl Pilot for Human{
fn fly(&self){
println!("This is your captain speaking.");
}
}
impl Wizard for Human{
fn fly(&self){
println!("Up!");
}
}
impl Human{
fn fly(&self){
println!("*waving arms furiously*");
}
}
这里2个特征Pilot和Wizard有fly方法,实现那两个特征的Human单元结构体,也拥有一个同名方法fly
优先调用类型上的方法
调用Human实例的fly时,编译器默认调用该类型中定义的方法:
fn main(){
let person = Human;
person.fly();
}
会打印Human的*waving arms furiouslu*,直接调用类型上定义的方法
调用特征上的方法
调用特征上的方法,需要使用显式调用的语法:
fn main(){
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}
This is your captain speaking.
Up!
*waving arms furiously*
显式调用时,编译器就可以根据调用的类型(self)决定具体调用什么方法
如果方法没有self参数呢?关联函数
trait Animal{
fn baby_name()->String;
}
struct Dog;
impl Dog{
fn baby_name()->String{
String::from("Spot")
}
}
impl Animal for Dog{
fn baby_name()->String{
String::from("puppy")
}
}
fn main(){
println!("A baby dog is called a {}",Dog::baby_name());
}
Dog::baby_name()的调用方式显然不行,可以通过下面来查询其他动物对狗的称呼:
fn main(){
println!("A baby dog is called a {}",Animal::baby_name());
}
Error:
Animal有很多,需要使用完全限定语法
fn main(){
println!("A baby dog is called a {}",<Dog as Animal)::baby_name());
}
上面通过as关键字,向rust提供了类型注解,就是Animal就是Dog,而不是其他动物,因此最终会调用impl Animal for Dog中的方法
完全限定语法定义:
<Type as Trait>::function(receiver_if_method,next_arg,...);
上面定义中,一个是方法接收器receiver(3中self),只有方法才拥有,关联函数就没有receiver
完全限定语法可以用于任何函数或者方法调用,但是我们却很少用到,rust编译器能根据上下文自动推导出调用的路径,大多数时候,无需使用完全限定语法。只有当存在多个同名函数或方法,rust无法区分调用的目标函数时,该语法才能真正有用武之地
特征定义中的特征约束
有时候我们需要让特征A使用另一个特征B的功能,这种情况下,不仅仅要为类型实现A,还需要为类型实现B,这就是基特征
例如一个特征OutlinePrint,有一个方法,能够对当前的实现类型进行格式化输出
use std::fmt::Display;
trait OutlinePrint:Display{
fn outline_print(&self){
let output = self.to_string();
let len = output.len();
println!("{}","*".repeat(len+4));
println!("*{}*"," ".repeat(len+2));
println!("* {} *",output);
println!("*{}*"," ".repeat(len+2));
println!("{}","*".repeat(len+4));
}
}
这里有一个眼熟的语法:OutlinePrint:Display,就像之前讲过的特征约束,只不过用于特征定义中而不是函数的参数中,是的,某种意义上,和特征约束很类似,都用来说明一个特征需要实现另一个特征,激素hi说需要实现OutlinePrint特征,首先需要实现Display特征
假如没有这个特征约束,那么self.to_string还能调用吗(to_string方法会为实现Display特征的类型自动实现)?编译器是不愿意的,会报错说当前作用域中找不到用于&Self类型的方法to_string
struct Point{
x:i32,
y:i32,
}
impl OutlinePrint for Point{}
因为Point没有实现Display特征,Error as:
OK满足你:
use std::fmt;
impl fmt::Display for Poinht{
fn fmt(&self,f:&mut fmt::Formatter)->fmt::Result{
write!(f,"({},{})",self.x,self.y) }
}
上面代码为Point实现了Display特征,那么to_string方法就会自动实现:最终获得字符串是通过这里的fmt获得的
在外部类型上实现外部特征(newtype)
trait中提到了孤儿规则,简而言之,就是特征或者类型必须至少有一个是本地的,才能在该类型上实现特征
使用newtype来绕过孤儿规则:为一个tuple struct创建新类型,该元组结构体封装有一个字段,希望实现特征的具体类型
该封装类型是本地的,可以为该类型实现外部的特征:
newtype不仅能实现以上的功能,而且在运行时无任何性能损耗,编译期间,该类型会被自动忽略
下面的例子:
有一个动态数组类型:Vec<T>,定义在标准库中,还有一个特征Display,也定义在标准库中,若是没有newtype,我们无法为Vec<T>实现Display
编译器给了提示:define and implement a trait or new type instead,重新定义一个特征,或者使用new Type,前者不可行,后者:
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper{
fn fmt(&self,f:&mut fmt::Formatter)->fmt::Result{
write!(f,"[{}]",self.0.join("."))
}
}
fn main(){
let w = Wrapper(vec![String::from("hello"),String::from("world")]);
}
其中,struct Wapper(Vec<String>)就是一个元组结构体,定义了一个newtypeWrapper,代码很简单
new type的缺点->访问数组的时候,很啰嗦:self.0.join(","):需要先从Wrapper中取出数组:self.0,然后才能执行join方法
类似的,任何数组上的方法,都无法直接调用,需要先用self.0取出数组,然后再进行调用
但是rust提供了一个特征叫做Deref,实现该特征之后,可以自动座一层类似类型转换的操作,将Wrapper变成Vec<String>来使用。这样就可以像直接使用数组一样来使用Wrapper,而无需为每一个操作都添加上self.0
同时,如果不想Wrapper暴漏底层数组的所有方法,还可以为Wrapper重载这些方法,实现隐藏的目的
这里没懂->看视频吧