5.1 定义和实例化-Struct

Struct示例(与C++类似)

1
2
3
4
5
6
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

在创建示例时,不需要按照声明里的顺序来初始化每个字段,例如

1
2
3
4
5
6
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("Alice"),
active: true,
sign_in_count: 1,
};
  • 所有字段都必须被初始化!

如果要访问实例的某个值采取点标记法,例如

1
let email = user1.email;

如果需要改变struct中的值,需要在声明的时候保证其是mutable,例如

1
2
3
4
5
6
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("Alice"),
active: true,
sign_in_count: 1,
};
  • mut关键字加在实例这里,而不是Struct声明中
  • 一旦该实例是可变的,所有字段都必须可变

struct可以作为函数的返回值,例如

1
2
3
4
5
6
7
8
fn build_user(email: String, user: String) -> User {
User {
email: email,
username: user,
active: true,
sign_in_count: 1,
}
}

当字段名和字段对应的变量名相同时,可以适用字段初始化简写,例如

1
2
3
4
5
6
User {
email, // abbrv.
username: user,
active: true,
sign_in_count: 1,
}

当我们希望创建一个实例2,它和已有的实例1有部分内容重叠,那么可以采用struct的更新语法来快速创建

1
2
3
4
5
let mut user2 = User {
email: String::from("bob@example.com"),
username: String::from("Bob"),
..user1
};

Tuple struct

tuple struct是另一种数据结构,兼有tuple和struct的特点:

  • 整体有名字,里面元素没名字
  • 适用于想给整个tuple起名字但不希望它和其它tuple一样的情况

例如

1
2
3
4
5
6
7
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
  • 这里blackorigin是不同的tuple struct的实例

Tuple struct的行为更像struct,可以使用模式匹配,也可以使用点索引


Unit-Like struct

  • 这种struct没有任何字段,类似于单位类型的地位

  • 适用于在某个类型上实现trait,但在里面有没有想存放的数据


Struct的所有权

每个struct实例拥有实例里所有的数据,所以只要该实例有效,里面字段也有效

  • struct也可以存放引用,需要借助生命周期
  • 生命周期保证只要struct的实例是有效的,里面引用也是有效的

5.2 struct示例

实现一个计算长方形面积的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rectangle = Rectangle {
width: 3,
height: 4,
};
println!("area = {}", area(&rectangle));
}

fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}

如果我们想使用下面的语句

1
println!("{}", rectangle);
  • 因为我们定义的struct还没有实现std::fmt::Display这个trait
  • 大多数基本数据结果是默认实现的,所以可以用formater来打印

一个解决方案是使用std::fmt::Debug这个trait

1
println!("{:?}", rectangle);

这需要我们实现Debug方法,可以在定义struct时声明

1
2
3
4
5
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
  • 其中derive是派生的意思

如果希望按列打印struct的实例,可以适用

1
println!("{:#?}", rectangle);

5.3 struct里的方法

我们可以在struct里定义方法(语法与函数定义类似),类似于面向对象里的类成员函数

  • 方法是在struct(或enum或trait)的上下文里定义
  • 其第一个参数为self

例如前面计算面积的例子,我们可以采用

1
2
3
4
5
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
  • 这里impl为implementation的缩写,用于定义名为Rectangle的struct的方法

  • self会被推断为struct类型本身

  • 方法里的参数可以不选择borrow,即self&mut self都是可以的


在C++中,一般使用object->func()或者(*object).func()来调用对象的方法(后者先解引用)

在Rust中,编译器会根据情况自动添加&&mut,或*来匹配方法的签名,例如

  • p1.distance(&p2)(&p1).distance(&p2)是等价的

类似的,也可以定义多个参数的方法,例如

1
2
3
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}

关联函数

我们可以在impl里面定义不以&self作为第一个参数的函数,称为关联函数(不是方法),例如

1
String::from()

一般用作构造器来创建实例,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}


fn main() {
let rectangle = Rectangle::square(4);
println!("area = {}", rectangle.area());
}
  • 其中的Rectangle::square()就是一个关联函数
  • 这里的符号::一般出现在关联函数中,此外,它也会出现在命名空间里

此外,每个struct允许定义多个impl