Rust学习进阶。
泛型
两个只在名称和签名中类型有所不同的函数
1 2 3 4 5 6 7 8 9
| struct Point<T> { x: T, y: T, }
fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; }
|
用T表示泛型
1 2 3 4 5 6 7 8 9 10
| struct Point<T, U> { x: T, y: U, }
fn main() { let both_integer = Point { x: 5, y: 10 }; let both_float = Point { x: 1.0, y: 4.0 }; let integer_and_float = Point { x: 5, y: 4.0 }; }
|
如果有不同类型需求,需要这样用U
枚举也可以拥有多个泛型类型。第九章使用过的 Result
枚举定义就是一个这样的例子:
1 2 3 4
| enum Result<T, E> { Ok(T), Err(E), }
|
Result
枚举有两个泛型类型,T
和
E
。Result
有两个成员:Ok
,它存放一个类型 T
的值,而
Err
则存放一个类型 E
的值。这个定义使得
Result
枚举能很方便的表达任何可能成功(返回 T
类型的值)也可能失败(返回 E
类型的值)的操作。
1 2 3 4 5
| impl Point<f32> { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } }
|
这样可以为不同的类型单独设计不同的方法
trait
使用trait定义一个特征:
1 2 3
| trait HasArea { fn area(&self) -> f64; }
|
trait里面的函数可以没有函数体,实现代码交给具体实现它的类型去补充:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct Circle { x: f64, y: f64, radius: f64, }
impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } }
fn main() { let c = Circle { x: 0.0f64, y: 0.0f64, radius: 1.0f64, }; println!("circle c has an area of {}", c.area()); }
|
trait与泛型
我们了解了Rust中trait的定义和使用,接下来我们介绍一下它的使用场景,从中我们可以窥探出接口这特性带来的惊喜
我们知道泛型可以指任意类型,但有时这不是我们想要的,需要给它一些约束。
泛型的trait约束
1 2 3 4
| use std::fmt::Debug; fn foo<T: Debug>(s: T) { println!("{:?}", s); }
|
Debug是Rust内置的一个trait,为"{:?}"实现打印内容,函数foo
接受一个泛型作为参数,并且约定其需要实现`Debug
多trait约束
可以使用多个trait对泛型进行约束:
1 2 3 4 5
| use std::fmt::Debug; fn foo<T: Debug + Clone>(s: T) { s.clone(); println!("{:?}", s); }
|
<T: Debug + Clone>
中Debug
和Clone
使用+
连接,标示泛型T
需要同时实现这两个trait。
where关键字
约束的trait增加后,代码看起来就变得诡异了,这时候需要使用where
从句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| use std::fmt::Debug; fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) { x.clone(); y.clone(); println!("{:?}", y); }
fn foo<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y); }
fn foo<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y); }
|
trait与内置类型
内置类型如:i32
,
i64
等也可以添加trait实现,为其定制一些功能:
1 2 3 4 5 6 7 8 9 10 11
| trait HasArea { fn area(&self) -> f64; }
impl HasArea for i32 { fn area(&self) -> f64 { *self as f64 } }
5.area();
|
这样的做法是有限制的。Rust 有一个“孤儿规则”:当你为某类型实现某 trait
的时候,必须要求类型或者 trait 至少有一个是在当前 crate
中定义的。你不能为第三方的类型实现第三方的 trait 。
在调用 trait 中定义的方法的时候,一定要记得让这个 trait
可被访问。
1 2 3 4
| let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt"); let buf = b"whatever"; let result = f.write(buf); # result.unwrap();
|
这里是错误:
1 2 3
| error: type `std::fs::File` does not implement any method in scope named `write` let result = f.write(buf); ^~~~~~~~~~
|
我们需要先use这个Write trait:
1 2 3 4 5 6
| use std::io::Write;
let mut f = std::fs::File::open("foo.txt").expect("Couldn’t open foo.txt"); let buf = b"whatever"; let result = f.write(buf); # result.unwrap();
|
这样就能无错误地编译了。
trait的默认方法
1 2 3 4 5
| trait Foo { fn is_valid(&self) -> bool;
fn is_invalid(&self) -> bool { !self.is_valid() } }
|
is_invalid
是默认方法,Foo
的实现者并不要求实现它,如果选择实现它,会覆盖掉它的默认行为。
trait的继承
1 2 3 4 5 6 7
| trait Foo { fn foo(&self); }
trait FooBar : Foo { fn foobar(&self); }
|
这样FooBar
的实现者也要同时实现Foo
:
1 2 3 4 5 6 7 8 9
| struct Baz;
impl Foo for Baz { fn foo(&self) { println!("foo"); } }
impl FooBar for Baz { fn foobar(&self) { println!("foobar"); } }
|
derive属性
Rust提供了一个属性derive
来自动实现一些trait,这样可以避免重复繁琐地实现他们,能被derive
使用的trait包括:Clone
,
Copy
, Debug
, Default
,
Eq
, Hash
, Ord
,
PartialEq
, PartialOrd
1 2 3 4 5 6
| #[derive(Debug)] struct Foo;
fn main() { println!("{:?}", Foo); }
|
impl Trait
在版本1.26 开始,Rust提供了impl Trait
的写法,作为和Scala
对等的既存型别(Existential Type)
的写法。
在下面这个写法中,fn foo()
将返回一个实作了Trait
的trait。
1 2 3 4 5 6 7 8 9
| fn foo() -> Box<Trait> { }
fn foo() -> impl Trait { }
|
相较于1.25
版本以前的写法,新的写法会在很多场合中更有利于开发和执行效率。
impl Trait 的普遍用例
1 2 3 4 5 6 7 8 9 10 11
| trait Trait { fn method(&self); }
impl Trait for i32 { }
impl Trait for f32 { }
|
利用Box
会意味:即便回传的内容是固定的,但也会使用到动态内存分配。利用impl Trait
的写法可以避免便用Box。
1 2 3 4 5 6 7 8 9
| fn foo() -> Box<Trait> { Box::new(5) as Box<Trait> }
fn foo() -> impl Trait { 5 }
|
其他受益的用例
闭包:
1 2 3 4 5 6 7 8 9
| fn foo() -> Box<Fn(i32) -> i32> { Box::new(|x| x + 1) }
fn foo() -> impl Fn(i32) -> i32 { |x| x + 1 }
|
传参:
1 2 3 4 5
| fn foo<T: Trait>(x: T) {
fn foo(x: impl Trait) {
|