一、Rust中的Trait
学习c++的人都知道Traits,萃取技术,它主要是用来得到具体的数据类型。在Rust中,这个Trait,当做了一种“特性”,更类似于接口这个概念。从宏观上讲,接口比c++的Traits更高级更广大了一些。不过本质上,仍然是对一些具体的性质的处理和获取。如果单纯的从Rust来看,可能Trait更像是函数的“萃取”。用Rust的表述来说,就是函数的签名。
看一下《RustPrimer》中的例子:
trait HasArea {
fn area(&self) -> f64;
}
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct Square {
x: f64,
y: f64,
side: f64,
}
impl HasArea for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
fn print_area<T: HasArea>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
对泛型应用来说,一定是通过约束保证T中必须有一个HasArea中的area()函数。
同样,对多个特性限定(multiple trait bounds)也是可以实现的,用+号就可以:
use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
fn bar<T, K>(x: T, y: K)
where T: Clone,
K: Clone + Debug
{
x.clone();
y.clone();
println!("{:?}", y);
}
那么Trait的应用场景是哪些呢?
1、接口的应用,这和其它语言的抽象接口层没有本质的不同。
2、泛型的限定引用,通过它来指定泛型的实现特点
3、一种抽象的数据类型,类似于Java的Object,可以动态的获得实际的运行类型。
4、标签的约束。
在Rust中有一个要求,即你的Trait在应用时,在本Crate中必须有一个实现或者Trait本身,这句话老别扭了,说白话就是,不允许你同时改别人的库里的类型和Trait,反过来也是如此。举一个例子,你不能实现Vec中的Display。因为这两个都是标准库里的,你不允许同时操作。它还有如下几条的限制:
1、特性在当前作用域内,才可以实现。这个在下面的Sealed中还会阐述
2、特性或impl,只能在当前的Box内起作用。
3、带有特性约束的泛型函数使用单态化实现 (monomorphization)。
4、静态分发的 (statically dispatched)
学过c++的可能更容易理解所谓的单态化,就是模板的多个具现实例或者说编译器会生成每个不同的实际类型的代码实例。所以单态化往往意味着代码的膨胀,名词太多了。
二、继承和密封
学过Java的都知道,接口是可以继承的,在Rust语言中Trait也是可以继承的:
trait Foo {
fn foo(&self);
// default method
fn bar(&self) { println!("We called bar."); }
}
// inheritance
trait FooBar : Foo {
fn foobar(&self);
}
struct Baz;
impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}
其实继承是所有面向对象编程都不可回避的问题,所以Trait有这种情况也是想当然的。
在其它语言中,有final这种关键字,扩大一点或者说protected这种关键字。同样,对待Trait,也需要一种差不多的东西来搞事情,就有了Seal Trait,它可以使用不放开的继承依赖(私有继承Trait)或者是拥有私有成员的Trait,来保证整个体系的安全性
mod nosub {
mod private {
pub trait Sealed {}
impl Sealed for String {}
}
pub trait nosub private::Sealed {
fn done(&self);
}
impl nosub for String {
fn done(&self) {
dbg!(self);
}
}
}
另外在Rust中还存在一种标签trait,主要有五种,它们在标准库std::marker模块中定义:
Sized trait:用来标示编译期可确定大小的类型
Unsize trait:目前该trait为实验特性,用于标示动态大小类型
Copy trait:用来标示可以安全地按位复制其值的类型。
Send trait:用来标示可以跨线程安全通信的类型
Sync trait:用来标示可以在线程间安全共享引用的类型。
三、泛型的应用
看一下Trait在泛型的应用,也是前面提到的对泛型的一个线束,这玩意儿和c++的模板有点相似。泛型和多态是有关系的,不同的语言处理起来可能稍微有区别,在下一篇文章中会具体分析一个Trait的动态和静态分发,这里只是简单的说明一下在泛型中的应用:
// generic functions
fn make_pair<T, U>(a: T, b: U) -> (T, U) {
(a, b)
}
let couple = make_pair("man", "female");
// generic structs
struct Point<T> {
x: T,
y: T,
}
let int_origin = Point { x: 0, y: 0 };
let float_origin = Point { x: 0.0, y: 0.0 };
文中所有代码都来自《RustPrimer》,非常感谢!
四、总结
工作的压力有点大,学习的压力也不小,平衡二者,有点小难,可是,难就不做了么?NO,NO,NO!“为之,则难者亦易矣!”
努力吧,归来的少年!