Rust 特型与泛型
Vec<T>
是泛型的:你可以创建任意类型值的向量,包括在你的程序中定义的连 Vec 的作者都不曾设想的类型。- 许多类型有
.write()
方法,包括 File 和 TcpStream。你的代码可以通过引用来获取任意写入器,并向它发送数据。你的代码不必关心写入器的类型。以后,如果有人添加了一种新型写入器,你的代码也能够直接支持。
Java 中很常用我就简单过了
特型(trait
)是 Rust 体系中的接口或抽象基类。
trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
...
}
如何使用
use std::io::Write;
fn say_hello(out: &mut dyn Write) -> std::io::Result<()> {
out.write_all(b"hello world\n")?;
out.flush()
}
泛型是 Rust 中多态的另一种形式。
/// 给定两个值,找出哪个更小
fn min<T: Ord>(value1: T, value2: T) -> T {
if value1 <= value2 {
value1
} else {
value2
}
}
此函数中的 表明 min 函数可以与实现了 Ord 特型的任意类型(任意有序类型)T 的参数一起使用。
使用特型
特型是一种语言特性,我们可以说某类型支持或不支持某个特型。大多数情况下,特型代表着一种能力,即一个类型能做什么。
其实感觉就很像是 Java 中的抽象类,其他类实现了抽象那么就有了该特性.
- 实现了 std::io::Write 的值能写出一些字节。
- 实现了 std::iter::Iterator 的值能生成一系列值。
- 实现了 std::clone::Clone 的值能在内存中克隆自身。
- 实现了 std::fmt::Debug 的值能用带有 {:?} 格式说明符的 println!() 打印出来。
[!IMPORTANT]
特型本身必须在作用域内。否则,它的所有方法都是不可见的:
//错误
let mut buf: Vec<u8> = vec![];
buf.write_all(b"hello")?; // 错误:没有名为`write_all`的方法
//在这种情况下,编译器会打印出一条友好的错误消息,建议添加 use std::io::Write;,这确实可以解决问题:
use std::io::Write;
let mut buf: Vec<u8> = vec![];
buf.write_all(b"hello")?; // 正确
特型对象
在 Rust 中使用特型编写多态代码有两种方法:特型对象和泛型。
Rust 不允许 dyn Write
类型的变量:
use std::io::Write;
let mut buf: Vec<u8> = vec![];
let writer: dyn Write = buf; // 错误:`Write`的大小不是常量
变量的大小必须是编译期已知的,而那些实现了 Write 的类型可以是任意大小。
对特型类型(如 writer)的引用叫作特型对象。
let mut buf: Vec<u8> = vec![];
let writer: &mut dyn Write = &mut buf; // 正确
泛型函数与类型参数
通过指定函数类型,进行设置输入函数类型.
trait Car {
fn new() -> Self
where
Self: Sized;
fn drive(&self);
}
trait Car2 {
fn new() -> Self
where
Self: Sized;
fn drive(&self);
}
struct Benz {
name: String,
model: String,
year: i32,
}
struct BMW {
name: String,
model: String,
year: i32,
}
impl Car2 for BMW {
fn new() -> Self {
BMW {
name: "BMW".to_string(),
model: "X5".to_string(),
year: 2020,
}
}
fn drive(&self) {
println!("{} {} is driving to {}", self.name, self.model, self.year);
}
}
impl Car for Benz {
fn new() -> Self {
Benz {
name: "Benz".to_string(),
model: "S500".to_string(),
year: 2020,
}
}
fn drive(&self) {
println!("{} {} is driving to {}", self.name, self.model, self.year);
}
}
fn drive_car<C: Car>(car: C) {
car.drive();
}
//重要 通过 where 来指定每个特性属于什么
fn drive_car2<C, D>(car: C, car2: D)
where
C: Car,
D: Car2,
{
car.drive();
car2.drive();
}
fn main() {
drive_car2(Benz::new(), BMW::new());
}
使用哪一个
关于是使用特型对象还是泛型代码的选择相当微妙。由于这两个特性都基于特型,因此它们有很多共同点。
trait Vegetable {
...
}
struct Salad<V: Vegetable> {
veggies: Vec<V>
}
struct Salad {
veggies: Vec<dyn Vegetable> // 错误:`dyn Vegetable`的大小不是常量
}
//使用 Box 限定一个
struct Salad {
veggies: Vec<Box<dyn Vegetable>>
}
定义与实现特型
定义特型很简单,给它一个名字并列出特型方法的类型签名即可。如果你正在编写游戏,那么可能会有这样的特型:
/// 角色、道具和风景的特型——游戏世界中可在屏幕上看见的任何东西
trait Visible {
/// 在给定的画布上渲染此对象
fn draw(&self, canvas: &mut Canvas);
/// 如果单击(x, y)时应该选中此对象,就返回true
fn hit_test(&self, x: i32, y: i32) -> bool;
}
impl Visible for Broom {
fn draw(&self, canvas: &mut Canvas) {
for y in self.y - self.height - 1 .. self.y {
canvas.write_at(self.x, y, '|');
}
canvas.write_at(self.x, self.y, 'M');
}
fn hit_test(&self, x: i32, y: i32) -> bool {
self.x == x
&& self.y - self.height - 1 <= y
&& y <= self.y
}
}
添加一个辅助方法来支持 Broom::draw(),就必须在单独的 impl 块中定义它:
impl Broom {
/// 供下面的Broom::draw()使用的辅助函数
fn broomstick_range(&self) -> Range<i32> {
self.y - self.height - 1 .. self.y
}
}
默认方法
如同 Java8 的功能,抽象类也是可以提供默认方法的,实现的对象可以不进行实现.
trait Car {
fn get_name(&self) -> &str;
fn say_name(&self) {
println!("I am a {}", self.get_name());
}
}
struct Benz;
impl Car for Benz {
fn get_name(&self) -> &str {
"Benz"
}
}
fn main() {
let cat = Benz;
cat.say_name();
}
特型与其他人的类型
Rust 允许在任意类型上实现任意特型,但特型或类型二者必须至少有一个是在当前 crate 中新建的。这意味着任何时候如果你想为任意类型添加一个方法,都可以使用特型来完成:
trait IsEmoji {
fn is_emoji(&self) -> bool;
}
/// 为内置的字符类型实现IsEmoji特型
impl IsEmoji for char {
fn is_emoji(&self) -> bool {
...
}
}
assert_eq!('$'.is_emoji(), false);
在实现特型时,特型或类型二者必须至少有一个是在当前 crate 中新建的。这叫作孤儿规则。
特型中的 Self
在第一个 impl 中,Self 只是 CherryTree 的别名,而在第二个 impl 中,它是 Mammoth 的别名。
pub trait Spliceable {
fn splice(&self, other: &Self) -> Self;
}
impl Spliceable for CherryTree {
fn splice(&self, other: &Self) -> Self {
...
}
}
impl Spliceable for Mammoth {
fn splice(&self, other: &Self) -> Self {
...
}
}
子特型
/// 游戏世界中的生物,既可以是玩家,也可以是
/// 其他小精灵、石像鬼、松鼠、食人魔等
trait Creature: Visible {
fn position(&self) -> (i32, i32);
fn facing(&self) -> Direction;
...
}
//每个实现了 Creature 的类型也必须实现 Visible 特型:
impl Visible for Broom {
...
}
impl Creature for Broom {
...
}
//可以用以下写法
trait Creature where Self: Visible {
...
}
类型关联函数
在大多数面向对象语言中,接口不能包含静态方法或构造函数,但特型可以包含类型关联函数,这是 Rust 对静态方法的模拟:
trait StringSet {
/// 返回一个新建的空集合
fn new() -> Self;
/// 返回一个包含`strings`中所有字符串的集合
fn from_slice(strings: &[&str]) -> Self;
/// 判断这个集合中是否包含特定的`string`
fn contains(&self, string: &str) -> bool;
/// 把一个字符串添加到此集合中
fn add(&mut self, string: &str);
}
fn main{
let set1 = SortedStringSet::new();
let set2 = HashedStringSet::new();
}
如果想使用 &dyn StringSet 特型对象,就必须修改此特型,为每个未通过引用接受 self 参数的关联函数加上类型限界 where Self: Sized:
trait StringSet {
fn new() -> Self
where Self: Sized;
fn from_slice(strings: &[&str]) -> Self
where Self: Sized;
fn contains(&self, string: &str) -> bool;
fn add(&mut self, string: &str);
}
完全限定的方法调用
迄今为止,我们看到的所有调用特型方法的方式都依赖于 Rust 为你补齐了一些缺失的部分。
当两个方法具有相同的名称时。
生拼硬凑的经典示例是 Outlaw(亡命之徒),它具有来自不同特型的两个 .draw() 方法,一个用于将其绘制在屏幕上,另一个用于犯罪。
outlaw.draw(); // 错误:画(draw)在屏幕上还是拔出(draw)手枪?
Visible::draw(&outlaw); // 正确:画在屏幕上
HasPistol::draw(&outlaw); // 正确:拔出手枪
当无法推断 self 参数的类型时。
let zero = 0; // 类型未指定:可能为`i8`、`u8`……
zero.abs(); // 错误:无法在有歧义的数值类型上调用方法`abs`
i64::abs(zero); // 正确
将函数本身用作函数类型的值时。
let words: Vec<String> =
line.split_whitespace() // 迭代器生成&str值
.map(ToString::to_string) // 正确
.collect();
评论