本文属于我的 Rust 学习笔记 系列。
Rust 入门学习笔记以实际例子为主,讲解部分不是从零开始的,所以不建议纯萌新观看,读者最好拥有任意一种面向对象语言的基础,然后自己多多少少看过 Rust 的基本语法,刷过一点 rustlings。
来源:原子之音。当然也包含个人的一些补充。
视频
代码
Rust 进阶学习笔记以及实战的来源则五花八门,将会标注在下一行⬇️。
特质
特质(Traits,有的地方也翻译成特征)是一种定义方法签名的机制。
特质允许提供一组方法的签名,但不提供具体实现。这些方法签名可以包括参数和返回类型,可选择提供或不提供默认实现(通常不提供)。
任何类型都可以实现特质,只要提供特质中定义的所有方法,使得我们可以为不同类型提供相同的行为。此时需要显式声明特质中定义的方法。
特质的特点
- 内置常量:可以内置常量(const),其生命周期是静态的
- 默认实现:可以提供默认方法实现,如果类型没有提供自定义实现,就会使用默认实现
- 多重实现:类型可以实现多个特质,将不同行为组合在一起。
- 特质边界:特质可以在泛型中作为类型约束。也就是说,特质可用于限定泛型类型的范围,泛型类型必须实现了这些特质。
- 特质别名:特质支持别名(alias),为复杂的特质组合创建简洁的别名,方便引用。
例子
trait Greeter {
fn greet(&self);
fn hello() {
println!("hello");
}
}
struct Person {
name: String,
}
impl Greeter for Person {
fn greet(&self) {
println!("greet {}", self.name);
}
}
fn main() {
let person = Person {
name: "xxx".to_owned(),
};
person.greet();
Person::hello();
}
特质对象和 Box
特质对象
特质对象(Trait Object)是实现了特定特质的类型的实例。
和面向对象语言的对象不同,特质对象是在运行时动态分配的“对象”。也称作“运行时泛型”(普通的泛型类型在编译期就分配了),比泛型更灵活。
在集合中可以混用一些不同的类型对象,方便处理相似数据。
虽然可能存在一定性能损耗,但一般还是建议使用特质对象而非泛型。
dyn 关键字
Rust 中,dyn
关键字用于声明特质对象的类型。特质对象的类型在编译时是未知的,为了让编译器知道正在处理的是特质对象,需要在特质名称前面添加dyn
关键字。
也就是说,dyn
关键字的作用是指示编译器处理特质对象。
特质对象的数据传输
- 不可变引用
&dyn Trait
- 可变引用
&mut dyn Trait
(很少用到)
- 所有权转移
Box<dyn Trait>
Box
特质需要用Box<dyn Trait>
实现所有权转移(即 Move)。如果需要在函数调用之间传递特质的所有权,并且希望避免在栈上分配大量的内存,可以使用Box<dyn Trait>
。
例子
struct Obj {}
trait Overview {
fn overview(&self) -> String {
String::from("overview")
}
}
impl Overview for Obj {
fn overview(&self) -> String {
String::from("Obj")
}
}
fn call_obj(item: &impl Overview) {
println!("Overview {}", item.overview());
}
fn call_obj_box(item: Box<dyn Overview>) {
println!("Overview {}", item.overview());
}
trait Sale {
fn amount(&self) -> f64;
}
struct Common(f64);
impl Sale for Common {
fn amount(&self) -> f64 {
self.0
}
}
struct TenDiscount(f64);
impl Sale for TenDiscount {
fn amount(&self) -> f64 {
self.0 - 10.0
}
}
struct TenPercentDiscount(f64);
impl Sale for TenPercentDiscount {
fn amount(&self) -> f64 {
self.0 * 0.9
}
}
fn calculate(sales: &Vec<Box<dyn Sale>>) -> f64 {
sales.iter().map(|sale| sale.amount()).sum()
}
fn main() {
let a = Obj {};
call_obj(&a);
println!("{}", a.overview());
let b_a = Box::new(Obj {});
call_obj_box(b_a);
let c: Box<dyn Sale> = Box::new(Common(100.0));
let t1: Box<dyn Sale> = Box::new(TenDiscount(100.0));
let t2: Box<dyn Sale> = Box::new(TenPercentDiscount(100.0));
let sales: Vec<Box<dyn Sale>> = vec![c, t1, t2];
println!("pay {}", calculate(&sales)); }
特质对象和泛型
泛型能够提供特质对象的另一种写法。
写法
- 单个特质
- impl 写法:可以是不同类型:
fn call(item1: &impl Trait, item2: &impl Trait);
- 泛型写法:同一泛型必须是相同类型:
fn call_generic<T: Trait>(item1: &T, item2: &T);
impl 写法是一种语法糖,实际上的完整写法是泛型写法,也常被称作特质约束(Trait Bound)
- 多个特质
- 语法糖形式:
fn call(item1: &(impl Trait + AnotherTrait));
- 特质约束(推荐):
fn call_generic<T: Trait + AnotherTrait>(item1: &T);
- where 约束(更推荐,清晰):
fn call_generic<T>(item: &T) where T: Trait + AnotherTrait,
例子
trait Overview {
fn overview(&self) -> String {
String::from("Course")
}
}
trait Another {
fn hell(&self) {
println!("welcome to hell");
}
}
struct Course {
headline: String,
author: String,
}
impl Overview for Course {}
impl Another for Course {}
struct AnotherCourse {
headline: String,
author: String,
}
impl Overview for AnotherCourse {}
fn call_overview(item: &impl Overview) {
println!("Overview {}", item.overview());
}
fn call_overview_generic<T: Overview>(item: &T) {
println!("Overview {}", item.overview());
}
fn call_overviewT(item: &impl Overview, item1: &impl Overview) {
println!("Overview {}", item.overview());
println!("Overview {}", item1.overview());
}
fn call_overviewTT<T: Overview>(item: &T, item1: &T) {
println!("Overview {}", item.overview());
println!("Overview {}", item1.overview());
}
fn call_mul_bind(item: &(impl Overview + Another)) {
println!("Overview {}", item.overview());
item.hell();
}
fn call_mul_bind_generic<T>(item: &T)
where
T: Overview + Another,
{
println!("Overview {}", item.overview());
item.hell();
}
fn main() {
let c0 = Course {
headline: "xx".to_owned(),
author: "yy".to_owned(),
};
let c1 = Course {
headline: "ff".to_owned(),
author: "yy".to_owned(),
};
let c2 = AnotherCourse {
headline: "ff".to_owned(),
author: "yz".to_owned(),
};
call_overview(&c1);
call_overview_generic(&c1);
call_overviewT(&c1, &c2);
call_overviewTT(&c1, &c0);
call_overviewT(&c1, &c0);
call_mul_bind(&c1);
call_mul_bind_generic(&c1);
}
重载操作符
Rust 重载只需要实现相应的特质。
示例:为结构体实现加号
use std::ops::Add;
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
impl<T> Add for Point<T>
where
T: Add<Output = T>,
{
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Point {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
fn main() {
let i1 = Point { x: 1, y: 2 };
let i2 = Point { x: 1, y: 3 };
let sum = i1 + i2;
println!("{:?}", sum); let f1 = Point { x: 1.0, y: 2.2 };
let f2 = Point { x: 1.0, y: 3.0 };
let sum = f1 + f2; println!("{:?}", sum);
}
多态和继承
继承
Rust 不支持面向对象,因此也不支持传统的继承概念,只是在思想上可以使用特质通过层级化的方式来完成继承的需求。
Rust 选择了函数化的编程方式,即通过组合和委托来平替继承。
多态
多态并非面向对象独有的概念,它通常是指同一个方法可以根据对象的不同类型表现出不同的行为。
多态允许一个接口或方法在不同的上下文中表现出不同的行为,这样做的好处是可以提高代码的灵活性和可扩展性,使得代码易于维护和理解。
Rust 中的多态无处不在。
例子
use std::collections::VecDeque;
trait Driver {
fn drive(&self);
}
struct Car;
impl Driver for Car {
fn drive(&self) {
println!("Car is driving");
}
}
struct SUV;
impl Driver for SUV {
fn drive(&self) {
println!("SUV is driving");
}
}
fn road(vehicle: &dyn Driver) {
vehicle.drive();
}
trait Queue {
fn len(&self) -> usize;
fn push_back(&mut self, n: i32);
fn pop_front(&mut self) -> Option<i32>;
}
trait Deque: Queue {
fn push_front(&mut self, n: i32);
fn pop_back(&mut self) -> Option<i32>;
}
#[derive(Debug)]
struct List {
data: VecDeque<i32>,
}
impl List {
fn new() -> Self {
let data = VecDeque::<i32>::new();
Self { data }
}
}
impl Deque for List {
fn push_front(&mut self, n: i32) {
self.data.push_front(n)
}
fn pop_back(&mut self) -> Option<i32> {
self.data.pop_back()
}
}
impl Queue for List {
fn len(&self) -> usize {
self.data.len()
}
fn push_back(&mut self, n: i32) {
self.data.push_back(n)
}
fn pop_front(&mut self) -> Option<i32> {
self.data.pop_front()
}
}
fn main() {
road(&Car);
road(&SUV);
let mut l = List::new();
l.push_back(1);
l.push_front(0);
println!("{:?}", l);
l.push_front(2);
println!("{:?}", l);
l.push_back(2);
println!("{:?}", l);
println!("{}", l.pop_back().unwrap());
println!("{:?}", l);
}
常见特质示例
#[derive(Debug, Clone, Copy)]
enum Race {
White,
Yellow,
Black,
}
impl PartialEq for Race {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Race::White, Race::White) => true,
(Race::Yellow, Race::Yellow) => true,
(Race::Black, Race::Black) => true,
_ => false,
}
}
}
#[derive(Debug, Clone)]
struct User {
id: u32,
name: String, race: Race,
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.name == other.name && self.race == other.race
}
}
fn main() {
let user = User {
id: 3,
name: "John".to_owned(),
race: Race::Yellow,
};
println!("{:?}", user); println!("{:#?}", user); let user2 = user.clone(); println!("{:#?}", user2);
println!("{}", user == user2); }