pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
泛型类型指定默认类型的语法是在声明泛型类型时使用 <PlaceholderType=ConcreteType>
Add trait
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
说明:
RHS 是一个泛型类型参数(“right hand side” 的缩写)。这里用到了默认泛型参数语法,如果实现 Add trait 时不指定 RHS 的具体类型,RHS 的类型将是默认的 Self 类型。
示例1:
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 });
}
示例2(覆盖默认泛型参数):
use std::ops::Add;
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 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。甚至直接在类型上实现开始已经有的同名方法也是可能的。
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;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}
在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 fly 实现。因为 fly 方法获取一个 self 参数,如果有两个 类型 都实现了同一 trait,Rust 可以根据 self 的类型计算出应该使用哪一个 trait 实现。
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() {
// 调用的是 Dog 上的关联函数
println!("A baby dog is called a {}", Dog::baby_name());
// 我们希望调用 Animal 上的 baby_name 方法
// 错误,关联函数没有 self 参数,无法计算具体是用哪一个实现
println!("A baby dog is called a {}", Animal::baby_name());
// 正确,完全限定语法
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
示例:
use std::fmt;
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 + 2));
println!("{}", "*".repeat(len + 4));
}
}
struct Point {
x: i32,
y: i32,
}
// 实现父 OutlinePrint 的同时,还需要单独实现 Display
impl OutlinePrint for Point {}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
用于在外部类型上实现外部 trait
示例:
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")]);
println!("w = {}", w);
}
注意:
如果希望新类型拥有其内部类型的每一个方法,为封装类型实现 Deref trait 并返回其内部类型。否则,只需自行实现所需的方法即可。
动态大小类型(dynamically sized types)的概念。这有时被称为 “DST” 或 “unsized types”,这些类型允许我们处理只有在运行时才知道大小的类型。
比如:
let s1: str = "Hello there!";
let s2: str = "How's it going?";
Rust 需要知道应该为特定类型的值分配多少内存,同时所有同一类型的值必须使用相同数量的内存。如果允许编写这样的代码,也就意味着这两个 str 需要占用完全相同大小的空间,不过它们有着不同的长度。这也就是为什么不可能创建一个存放动态大小类型的变量的原因。
字符串 虽然 &T 是一个储存了 T 所在的内存位置的单个值,&str 则是 两个 值:str 的地址和其长度。于是,&str 就有了一个在编译时可以知道的大小。 这里是 Rust 中动态大小类型的常规用法:他们有一些额外的元信息来储存动态信息的大小。这引出了动态大小类型的黄金规则:必须将动态大小类型的值置于某种指针之后。 此外,也可以将 str 与其它类型的指针结合:比如 Box 或 Rc。
Rust 有一个特定的 trait 来表明一个类型的大小在编译时可知。
示例1:
在泛型中,Rust 隐式的为每一个泛型函数增加了 Sized bound。
fn generic<T>(t: T) {
// --snip--
}
实际上被当作如下处理:
fn generic<T: Sized>(t: T) {
// --snip--
}
泛型函数默认只能用于在编译时已知大小的类型,如果在编译时不知道大小,可以使用如下特殊语法来放宽这个限制:
fn generic<T: ?Sized>(t: &T) {
// --snip--
}
trait 对象指向一个实现了我们指定 trait 的类型的实例,以及一个用于在运行时查找该类型的trait方法的表。我们可以使用 trait 对象代替泛型或具体类型。任何使用 trait 对象的位置,Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象的 trait。
在结构体或枚举中,结构体字段中的数据和 impl 块中的行为是分开的,不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 则 其更类似其他语言中的对象。不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。
1.使用 trait object
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
2.使用 泛型 + trait bounds
pub struct Screen<T: Draw> {
pub components: Vec<T>,
}
impl<T> Screen<T>
where T: Draw {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
注:
使用后者,限制了 Screen 实例只能有一种泛型实现,而使用 trait 对象的方法,一个 Screen 实例可以存放一个既能包含 Box<Button>
,也能包含 Box<TextField>
的 Vec<T>
。此外,从编译器角度,前者使用静态分发,后者使用动态分发。