5. Rust 结构体
5.1 定义结构体
struct
关键字用于定义结构体。与 C 类似,需要为所有字段定义名称和类型。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
注意,每个字段使用逗号分隔,即使是最后一个字段也是如此。
实例化结构体时,没有必要按照定义时的顺序指定字段的值,但需要指定字段的名称。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("acb@126.com"),
username: String::from("Alex"),
active: true,
sign_in_count: 1,
};
}
一旦结构体实例可变,则结构体实例中所有字段都可变。
与 ES6 类似,字段名和字段值相同时可以省略字段名。
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
还可以使用更新语法 ..
来创建一个实例,该实例的剩余字段与给定实例相同。
let user2 = User {
email: String::from("123.123.com"),
username: String::from("123"),
..user1
};
还有一种特殊的结构体,称为 元组结构体(Tuple Struct)。元组结构体有名称但没有字段名。
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
元组结构体的每个实例都有相同的类型,但不同的元组结构体实例之间是不兼容的。
还有一种特殊的结构体,称为 单元结构体(Unit-Like Struct)。或者称为空结构体,与 ()
类似。单元结构体没有字段,用于实现某种特定的 trait。
struct EmptyStruct;
一个结构体拥有所有权时,即拥有所有数据的所有权。如果结构体内存储引用但不使用生命周期就会报错。
5.2 实例:计算长方形面积
我们可以使用元组来存储长方形的宽和高。
fn main() {
let rect = (30, 50);
println!(
"The area of the rectangle is {} square pixels.",
area(rect)
);
}
fn area(dim: (u32, u32)) -> u32 {
dim.0 * dim.1
}
现在我们使用结构体来实现:
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("The area of the rectangle is {} square pixels.", area(&rect));
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.height
}
由于 area()
函数借用了 rect
,所以 rect
仍然可以继续使用。
有时我们需要调试结构体中的信息,可以使用 #[derive(Debug)]
注解。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("The area of the rectangle is {} square pixels.", area(&rect));
println!("{:?}", rect);
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.height
}
这将打印出 Rectangle { width: 30, height: 50 }
。可以使用 {:#?}
来更好地格式化输出。
可以通过实现 std::fmt::Debug
trait 来自定义调试输出。同样,可以通过实现 std::fmt::Display
trait 来自定义打印输出。
5.3 方法
方法与函数类似,但是它们是在结构体上定义的。
方法在 impl
块中调用。与 Python 类似,Rust 方法的第一个参数是 self
,但也可以获得其所有权,使用 self
,也可以获得可变引用 &mut self
。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("The area of the rectangle is {} square pixels.", rect.area());
println!("{:?}", rect);
}
Rust 会在调用方法时自动引用和解引用。
下面我们使用 can_hold()
方法来确认一个长方形是否可以容纳另一个长方形。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };
println!("rect1 can hold rect2: {}", rect1.can_hold(&rect2));
println!("rect1 can hold rect3: {}", rect1.can_hold(&rect3));
}
如果不使用 self
作为第一个参数,则此函数为关联函数,而不是方法。使用 ::
调用关联函数。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let square = Rectangle::square(3);
println!("{:?}", square);
}