内存布局
struct File {
name: String,
data: Vec<u8>,
}
fn main() {
let f1 = File {
name: String::from("f1.txt"),
data: Vec::new(),
};
}
上面定义的 File 结构体在内存中的排列如下图所示

引用类型的数据成员
生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。
如果你想在结构体中使用一个引用,就必须加上生命周期,否则就会报错:
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
编译器会抱怨它需要生命周期标识符:
error[E0106]: missing lifetime specifier
--> src/main.rs:2:15
|
2 | username: &str,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
1 ~ struct User<'a> {
2 ~ username: &'a str,
|
初始化
创建结构体实例赋值时,每个字段和let一样
#[derive(Debug)]
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
active: user1.active,
username: user1.username, // move occur
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
println!("{}", user1.active);
// 下面这行会报错
println!("{:?}", user1);
}
-> 运算符到哪去了:方法调用中的“自动引用和解引用”
在 C/C++ 语言中,有两个不同的运算符来调用方法:. 直接在对象上调用方法,而 -> 在一个对象的指针上调用方法,这时需要先解引用指针。换句话说,如果 object 是一个指针,那么 object->something() 和 (*object).something() 是一样的。
Rust 并没有一个与 -> 等效的运算符;相反,Rust 有一个叫 自动引用和解引用的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。
他是这样工作的:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 &、&mut 或 * 以便使 object 与方法签名匹配。也就是说,这些代码是等价的:
obj1不是引用类型
obj1.distance(); // normal
(&obj1).distance(); // just for presentation
p1是引用类型
(*p1).distance(); // normal as for grammer
p1.distance(); // more concise
这种自动引用的行为之所以有效,是因为方法有一个明确的接收者———— self 的类型。在给出接收者和方法名的前提下,Rust 可以明确地计算出方法是仅仅读取(&self),做出修改(&mut self)或者是获取所有权(self)。事实上,Rust 对方法接收者的隐式借用让所有权在实践中更友好。
举例(含“自动引用和解引用”的举例)
struct Point {
x: f64,
y: f64,
}
// `Point` 的关联函数都放在下面的 `impl` 语句块中
impl Point {
// 关联函数的使用方法跟构造器非常类似
fn origin() -> Point {
Point { x: 0.0, y: 0.0 }
}
// 另外一个关联函数,有两个参数
fn new(x: f64, y: f64) -> Point {
Point { x: x, y: y }
}
}
struct Rectangle {
p1: Point,
p2: Point,
}
impl Rectangle {
// 这是一个方法
// `&self` 是 `self: &Self` 的语法糖
// `Self` 是当前调用对象的类型,对于本例来说 `Self` = `Rectangle`
fn area(&self) -> f64 {
// 使用点操作符可以访问 `self` 中的结构体字段
let Point { x: x1, y: y1 } = self.p1;
let Point { x: x2, y: y2 } = self.p2;
// `abs` 是一个 `f64` 类型的方法,会返回调用者的绝对值
((x1 - x2) * (y1 - y2)).abs()
}
fn perimeter(&self) -> f64 {
let Point { x: x1, y: y1 } = self.p1;
let Point { x: x2, y: y2 } = self.p2;
2.0 * ((x1 - x2).abs() + (y1 - y2).abs())
}
// 该方法要求调用者是可变的,`&mut self` 是 `self: &mut Self` 的语法糖
fn translate(&mut self, x: f64, y: f64) {
self.p1.x += x;
self.p2.x += x;
self.p1.y += y;
self.p2.y += y;
}
}
// `Pair` 持有两个分配在堆上的整数
struct Pair(Box<i32>, Box<i32>);
impl Pair {
// 该方法会拿走调用者的所有权
// `self` 是 `self: Self` 的语法糖
fn destroy(self) {
let Pair(first, second) = self;// 这里的语法是模式匹配(变量名和字段名相同的简写)
// 模式匹配和let一样,这里会发生所有权转移
println!("Destroying Pair({}, {})", first, second);
// `first` 和 `second` 在这里超出作用域并被释放
}
}
fn main() {
let rectangle = Rectangle {
// 关联函数的调用不是通过点操作符,而是使用 `::`
p1: Point::origin(),
p2: Point::new(3.0, 4.0),
};
// 方法才是通过点操作符调用
// 注意,这里的方法需要的是 `&self` 但是我们并没有使用 `(&rectangle).perimeter()` 来调用,原因在于:
// 编译器会帮我们自动取引用
// `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)`
println!("Rectangle perimeter: {}", rectangle.perimeter());
println!("Rectangle area: {}", rectangle.area());
let mut square = Rectangle {
p1: Point::origin(),
p2: Point::new(1.0, 1.0),
};
// 错误!`rectangle` 是不可变的,但是这个方法要求一个可变的对象
// rectangle.translate(1.0, 0.0);
// 可以!可变对象可以调用可变的方法
square.translate(1.0, 1.0);
let pair = Pair(Box::new(1), Box::new(2));
pair.destroy();
// Error! 上一个 `destroy` 调用拿走了 `pair` 的所有权
// pair.destroy();
}