我们将在这一章学习更多高级功能
关联类型在trait定义中指定占位符类型
关联类型是一个方式,是一个将类型占位符与trait相关联,这样在trait 的方法签名中就可以使用这些占位符类型
pub trait Iterator {
type Item; //使用type关键字将占位类型Item与Iterator关联起来
fn next(&mut self)->Option<Self::Item>;//在方法签名中使用这些占位符类型
}
trait实现者会针对特定的实现在这个类型的位置指定相应具体类型(也就是用的时候才会明确)
关联类型与泛型有点相似,但它有什么新特点呢
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// -- snip --
}
}
我们为Counter实现了trait,注意:实现的时候要指定Item具体的类型,这里是u32
那如果使用泛型来执行上述过程呢?
pub trait Iterator<T> {
fn next(&mut self)->Option<T>;
}
使用泛型Iterator trait假想定义
为Counter实现使用泛型的Iterator,针对每一个具体类型都得写一个实现,但是使用关联类型的话,只需要指定一下Item的具体类型就可,所以使用泛型不是不可以,只是比较麻烦而已
impl Iterator<String> for Counter {
// -- snip --
}
impl Iterator<u32> for Counter {
// -- snip --
}
impl Iterator<bool> for Counter {
// -- snip --
}
默认泛型类型参数和运算符重载
使用泛型类型参数时,可以为泛型指定一个默认的具体类型。如果默认类型足够的话,就不用为具体类型实现trait了
泛型类型指定默认类型的语法时在声明泛型类型时使用<>PlaceholderType=ConcreteType>
这种情况一个非常好的例子是用于运算符重载
Rust并不支持自定义运算符或重载任意运算符,不过std::ops中列出的运算符和相应的trait可以实现类似的功能,比如下面的例子中,我们把两个结构体相加
use std::{ops::Add, process::Output};
#[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:3} + Point{x:2,y:3},Point{x:3,y:6})
}
trait Add(RHS=Self){
type Output;
fn add(self,rhs:RHS)->Self::Output;
}
在Add trait中的默认泛型,RHS是一个泛型类型参数,如果为具体类型实现add trait时不指定关联类型的具体类型,那就默认使用Self类型
我们再来看一个不使用默认类型的例子
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self,other:Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
小结:默认参数类型主要用于:
扩展类型而不破坏原有代码
在特定情况下自定义
完全限定语法与消歧义:调用相同名称的方法
Rust的trait可以同名,也可为一个类型同时实现多个trait。所以调用这些同名方法需要明确告知Rust我们要用哪一个
我们直接来看例子
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*");
}
}
fn main(){
let person = Human;
person.fly();
}
直接打印,默认调用类型上实现的方法
Running `target/debug/advancedfunction`
*waving arms furiously*
fn main(){
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}
Running `target/debug/advancedfunction`
This is your captain speaking.
Up!
*waving arms furiously*
其他的fly实现需要具体明确指定
然而关联函数是trait的一部分,但没有self参数
当统一作用域的两个类型实现了同一个trait,Rust就不能计算出我们期望的是哪一个类型,除非使用完全限定语法。例子如下
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());
}
Running `target/debug/advancedfunction`
A baby dog is called a Spot
这不是我们想要调用的方法,我们希望调用的是Dog上Animal trait实现的那部分的baby_name函数,这样能够打印出A baby dog is called a Puppy
fn main(){
println!("A baby dog is called a {}",Animal::baby_name());
}
error[E0283]: type annotations needed
--> src/main.rs:19:42
|
19 | println!("A baby dog is called a {}",Animal::baby_name());
| ^^^^^^^^^^^^^^^^^ cannot infer type
|
= note: cannot satisfy `_: Animal`
我们尝试调用Animal trait的baby_name函数,但是发生了编译错误。因为Animal::baby_name是关联函数而不是方法,它没有self参数。为了消除歧义,我们得使用完全限定语法
fn main(){
println!("A baby dog is called a {}",<Dog as Animal>::baby_name());
}
Running `target/debug/advancedfunction`
A baby dog is called a Puppy
在尖括号中我们提供了类型注解,将Dog作为Animal对待,来指定希望调用的是Dog上Animal trait实现中的baby_name函数
通常,完全限定语法定义为:
Type as Trait>::function(receiver_if_method,next_arg,...);
对于关联函数,其没有一个receiver,故只会有其他参数的列表。可以选择在任何函数或者方法调用处使用完全限定语法。然而,只有当存在多个同名实现而Rust需要帮助以便我们知道调用哪个实现时,才需要使用这个较为冗长的语法
父trait用于在另一个trait中使用某trait的功能
有时候我们需要在一个trait中使用另一个trait的功能,那这个被使用的trait就是我们实现trait的父(超)trait
我们来直接看例子:我们现在要创建一个带有outline_print方法的trait OutlinePrint,打印出如下带有*号框的值
**********
* *
* (1, 3) *
* *
**********
trait OutlinePrint: fmt::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+4));
}
}
我们定义了一个我们所期望的trait,但是呢,他希望使用Display trait的功能。所以类型必须实现Display trait,这样我们就可以使用to_string了
struct Point {
x: i32,
y: i32,
}
impl OutlinePrint for Point {}
error[E0277]: the trait bound `Point: std::fmt::Display` is not satisfied
--> src/main.rs:20:6
|
20 | impl OutlinePrint for Point {}
| ^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter;
try using `:?` instead if you are using a format string
|
= help: the trait `std::fmt::Display` is not implemented for `Point`
如果类型没有实现Display,那么为它实现OutlinePrint会发生如上错误
struct Point {
x:i32,
y:i32,
}
impl fmt::Display for Point{
fn fmt(&self,f:&mut fmt::Formatter) -> fmt::Result{
write!(f,"{},{}",self.x,self.y)
}
}
如果为类型实现Display trait,则可以成功编译
newtype 模式用以在外部类型上实现外部trait
在第十章,我们提到了孤儿规则,说明只要trait或类型对于当前crate是本地的话就可以在此类型上实现trait
但是newtype可以绕过这一规则,它需要在一个元组结构体中创建一个新类型。元组结构体带有一个字段,它是一个希望实现trait的类型的简单封装,这个封装对于crate来说是本地的,这样就可以在这个封装上实现trait
例如:想要在Vec 上实现Display,孤儿规则不允许。因为Display trait 和 Vec 都定义于我们的crate之外
但是可以创建一个包含Vec 实例的Wrapper 结构体,接着可以在Wrapper上实现Display 并使用Vec 的值
use std::fmt::{self, write};
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(","))
}
}
Display的实现使用self.0来访问其内部的Vec, 因为Wrapper 是元组结构体而Vec是结构体总位于索引0的项。接着就可以使用Wrapper中的Display的功能了
这个方法的缺点是,因为Wrapper是一个新类型,它没有定义于之上的方法,必须直接在Wrapper上实现Vec的所有方法,这样就可以代理到self.0上。这就允许我们完全像Vec那样对待Wrapper
如果希望新类型拥有其内部类型的每一个方法,为封装类型实现Deref trait并返回内部类型是一种解决方法。如果不希望,如为了限制封装类型的行为,则必须只自行实现所需的方法
上面便是new type模式如何于trait结合使用的,还有一个不涉及trait的实用模式