上节内容回顾
在# 我在外包学 rust——引用与借用中,对 rust 的引用和借用进行了介绍,同时对切片类型形成了一定的认识。引用和借用是一体两面的,解决了所有权传来传去不方便的问题。回顾一下引用规则:
引用规则
- 引用和借用是一体两面,你把东西借给别人用,也就是别人持有了对你这个东西的引用。
- 所有权型变量的作用域是从它定义时开始到所属那层花括号结束。
- 引用型变量的作用域是从它定义起到它最后一次使用时结束。
- 引用(不可变引用和可变引用)型变量的作用域不会长于所有权变量的作用域。这是肯定的,不然就会出现悬垂引用,这是典型的内存安全问题。
- 一个所有权型变量的不可变引用可以同时存在多个,可以复制多份。
- 一个所有权型变量的可变引用与不可变引用的作用域不能交叠,也可以说不能同时存在。
- 某个时刻对某个所有权型变量只能存在一个可变引用,不能有超过一个可变借用同时存在,也可以说,对同一个所有权型变量的可变借用之间的作用域不能交叠。
- 在有借用存在的情况下,不能通过原所有权型变量对值进行更新。当借用完成后(借用的作用域结束后),物归原主,又可以使用所有权型变量对值做更新操作了。
- 可变引用的再赋值,会执行移动操作。赋值后,原来的那个可变引用变量就不能用了。这有点类似于所有权的转移.因此一个所有权型变量的可变引用也具有所有权特征,它可以被理解为那个所有权变量的独家代理,具有排它性。
多级引用规则
- 对于多级可变引用,要利用可变引用去修改目标资源的值的时候,需要做正确的多级解引用操作。
- 只有全是多级可变引用的情况下,才能修改到目标资源的值。
- 对于多级引用(包含可变和不可变),打印语句中,可以自动为我们解引用正确的层数,直到访问到目标资源的值,这很符合人的直觉和业务的需求。
今天主要介绍 rust 的其中一种复合类型——结构体 struct。
结构体struct
什么是结构体
- 用 struct 关键字进行定义
- 由其他基础类型或复合类型组成
- 当所有字段(feild)同时实例化后,就生成了这个结构体的实例
示例
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
很像 TS 中的 interface 有没有。上面的User结构体的字段都是基础类型。
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
sign_in_count: 1,
active: true,
username: String::from("someusername123"),
};
}
上面的代码就是结构体的实例化了,也就是当它的所有字段都实例化后,就完成了结构体的实例化。 当然,结构体的字段也可以是结构体:
struct Class {
serial_number: u32,
grade_number: u32,
entry_year: String,
members: Vec<User>,
}
在实际应用中,结构体往往是一个程序的骨干,用来承载对目标问题进行建模和描述的重任。
注意:一旦 struct 的实例是可变的,那实例中所有的字段都是可变的。
结构体有哪些形式
-
命名结构体
变量与字段同名,可以偷懒,前端er们有没有想起 es6:
fn main() { let active = true; let username = String::from("someusername123"); let email = String::from("someone@example.com"); let user1 = User { active, // 这里本来应该是 active: active, username, // 这里本来应该是 username: username, email, // 这里本来应该是 email: email, sign_in_count: 1, }; }更新结构体部分字段:
fn main() { let mut user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; user1.email = String::from("anotheremail@example.com"); }另一种偷懒,..,依旧是 es6 啊同志们,前端er 狂喜:
fn main() { let active = true; let username = String::from("someusername123"); let email = String::from("someone@example.com"); let user1 = User { active, username, email, sign_in_count: 1, }; let user2 = User { email: String::from("another@example.com"), ..user1 // 注意这里,直接用 ..user1 }; } -
元组结构体 tuple struct
其实是一种匿名结构体,是元组和结构体的结合体。有类型名,但没有字段名。
struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0); } -
单元结构体 unit-like struct
单元结构体只有一个类型名,没有任何字段。创建时后面的花括号都可以省略。
struct ArticleModule; fn main() { let module = ArticleModule; // 请注意这一句,也做了实例化操作 }那没有字段的结构体有什么用呢?其实它就相当于定义了一种类型,它的名字就是一种信息,有类型名就可以进行实例化,承载很多东西。后面我们在代码中会经常看到单元结构体。 适用于对某个类型实现某个 trait,但里面有没有要存储的数据。
结构体中的所有权
部分移动:结构体中的部分字段是可以被移出去的。
#[derive(Debug)]
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u32,
}
fn main() {
let active = true;
let username = String::from("someusername123");
let email = String::from("someone@example.com");
let user1 = User {
active,
username,
email,
sign_in_count: 1,
};
let email = user1.email; // 在这里发生了partially moved
println!("{:?}", user1) // 这一句无法通过编译
}
报错信息:
error[E0382]: borrow of partially moved value: `user1`
--> src/main.rs:22:22
|
20 | let email = user1.email;
| ----------- value partially moved here
21 |
22 | println!("{:?}", user1)
| ^^^^^ value borrowed here after partial move
由于user1中的email的所有权被移动给 email,也就是发生了部分移动,打印user1会报错。
字段是引用类型:
struct User {
active: &bool,
username: &str,
email: &str,
sign_in_count: &u32,
}
这里要通过编译,需要标注生命周期。这是后面的内容了,还没学到 😂。
几乎所有的地方,rust 都会把问题一分为:一是所有权形式的表示,另一个是借用形式的表示,借用形式的表示又可以分为不可变借用和可变借用。
给结构体添加标注
上面代码#[derive(Debug)] 为 struct 添加标注,就可以打印整个结构体,这种语法在 rust 中叫做属性标注,具体来说这里用的是派生宏属性。派生宏作用在下面紧接着的结构体类型上,可以为结构体自动添加一些功能。
如果你学过Java,可能会非常眼熟,这跟Java中的标注语法非常像,功能也是类似的,都会对原代码的元素产生作用。不过,Rust这个特性作为一套完整的宏机制,要强大得多。它让Rust的语言表达能力又上了一个台阶。
面向对象特性
虽然rust 不是一门面向对象的语言,但是具有部分面向对象的特性。 rust 承载面向对象特性的主要类型就是结构体。通过 impl 关键字可以用来给结构体或其他类型实现方法,即关联到某个类型上的函数。
方法(实例方法)
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle { // 就像这样去实现
fn area(self) -> u32 { // area就是方法,被放在impl实现体中
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area() // 使用点号操作符调用area方法
);
}
// 输出
// The area of the rectangle is 1500 square pixels.
self
上面函数中的fn area(self) -> u32self 其实是self:Self的语法糖,Self是 rust 中一个特殊的类型名,表示正在被实现(impl)的类型。
impl Rectangle {
fn area1(self) -> u32 {
self.width * self.height
}
fn area2(&self) -> u32 {
self.width * self.height
}
fn area3(&mut self) -> u32 {
self.width * self.height
}
}
上面的三种方式都是可以的。
- self:Self 表示传入实例的所有权
- self:&Self 表示传入实例的不可变引用
- self:&mut Self 表示传入实例的可变引用
因为是标准用法,rust 帮我们简写成了 self、&self、&mut self。
调用的时候就是通过 实例.方法的方式调用:
rect1.area();
实例的引用也可以调用实例方法,且对于不可变引用,rust 会自动做正确的多级解引用操作:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
// 在这里,取了实例的引用
let r1 = &rect1;
let r2 = &&rect1;
let r3 = &&&&&&&&&&&&&&&&&&&&&&rect1; // 不管有多少层
let r4 = &&r1;
// 以下4行都能打印出正确的结果
r1.area();
r2.area();
r3.area();
r4.area();
}
对同一类型,impl 可以分开多次写。
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
关联函数(静态方法)
如果实现在类型上的函数,它的第一个参数不是self参数,那么它就叫做此类型的关联函数。
impl Rectangle {
fn numbers(rows: u32, cols: u32) -> u32 {
rows * cols
}
}
调用时,关联函数使用类型配合路径符 :: 来调用。
Rectangle::numbers(10, 10);
构造函数
Rust社区一般约定使用 new() 这个名字的关联函数作为构造函数,像下面这样把类型的实例化包起来。
impl Rectangle {
pub fn new(width: u32, height: u32) -> Self {
Rectangle {
width,
height,
}
}
}
let rect1 = Rectangle::new(30, 50);
Default
在对结构体做实例化的时候,Rust又给我们提供了一个便利的设施,Default。
#[derive(Debug, Default)] // 这里加了一个Default派生宏
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1: Rectangle = Default::default(); // 使用方式1
let rect2 = Rectangle::default(); // 使用方式2
println!("{:?}", rect1);
println!("{:?}", rect2);
}
// 打印出如下:
Rectangle { width: 0, height: 0 }
Rectangle { width: 0, height: 0 }
Default有两种使用方式,一种是直接用 Default::default(),第二种是用类型名 ::default(),它们的实例化效果是一样的。
可以看到,打出来的实例字段值都0,是因为u32类型默认值就是 0。对于通用类型,比如u32这种类型来说,取 0 是最适合的值了,想一想取其他值是不是没办法被大多数人接受?
但是,对于我们特定场景的Rectangle这种,我们可能希望给它赋一个初始的非 0 值。在Rust中,这可以做到,但是需要用到后面的知识。目前我们就可以先用约定的 new 关联函数+参数来达到我们的目的。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
pub fn new(width: u32, height: u32) -> Self {
Rectangle {
width,
height,
}
}
}
const INITWIDTH: u32 = 50;
const INITHEIGHT: u32 = 30;
fn main() {
// 创建默认初始化值的Rectangle实例
let rect1 = Rectangle::new(INITWIDTH , INITHEIGHT);
}
结尾
今天就到这里吧,累了,中午没睡着,下午略崩溃,我要去联调借口了。同志们拜拜,加油!! Passion~(有气无力)