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);
}每个结构体可以有多个 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
}
}5.4 结构体的高级用法
结构体更新语法
我们可以使用结构体更新语法基于现有实例创建新实例:
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("user1@example.com"),
username: String::from("user1"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("user2@example.com"),
..user1 // 使用 user1 的其余字段
};
// 注意:user1.username 已被移动到 user2
// println!("{}", user1.username); // 错误
println!("{}", user1.active); // 正确:bool 实现了 Copy
}结构体的所有权
结构体的字段可以拥有数据,也可以存储引用。当存储引用时,需要使用生命周期标注:
struct User<'a> {
username: &'a str,
email: &'a str,
sign_in_count: u64,
active: bool,
}
fn main() {
let username = String::from("user1");
let email = String::from("user1@example.com");
let user = User {
username: &username,
email: &email,
active: true,
sign_in_count: 1,
};
println!("{}", user.username);
}派生 trait
Rust 允许我们为结构体自动实现一些常用的 trait:
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone();
println!("{:?}", p1); // Debug trait
println!("p1 == p2: {}", p1 == p2); // PartialEq trait
}常用的可派生 trait:
Debug:允许使用{:?}格式化打印Clone:允许显式复制值Copy:允许隐式复制值(仅适用于简单类型)PartialEq和Eq:允许比较相等性PartialOrd和Ord:允许比较大小Hash:允许哈希
结构体方法的链式调用
可以设计方法返回 self 来支持链式调用:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
Rectangle { width, height }
}
fn set_width(mut self, width: u32) -> Self {
self.width = width;
self
}
fn set_height(mut self, height: u32) -> Self {
self.height = height;
self
}
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle::new(10, 20)
.set_width(30)
.set_height(40);
println!("Area: {}", rect.area());
}注意:这种模式会获取所有权,如果想保持引用,需要返回 &mut self:
impl Rectangle {
fn set_width(&mut self, width: u32) -> &mut Self {
self.width = width;
self
}
fn set_height(&mut self, height: u32) -> &mut Self {
self.height = height;
self
}
}
fn main() {
let mut rect = Rectangle::new(10, 20);
rect.set_width(30)
.set_height(40);
println!("Area: {}", rect.area());
}示例:构建一个简单的点和矩形系统
#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
fn distance(&self, other: &Point) -> f64 {
let dx = (self.x - other.x) as f64;
let dy = (self.y - other.y) as f64;
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Debug)]
struct Rectangle {
top_left: Point,
bottom_right: Point,
}
impl Rectangle {
fn new(top_left: Point, bottom_right: Point) -> Self {
Rectangle { top_left, bottom_right }
}
fn width(&self) -> i32 {
(self.bottom_right.x - self.top_left.x).abs()
}
fn height(&self) -> i32 {
(self.bottom_right.y - self.top_left.y).abs()
}
fn area(&self) -> i32 {
self.width() * self.height()
}
fn contains(&self, point: &Point) -> bool {
point.x >= self.top_left.x
&& point.x <= self.bottom_right.x
&& point.y >= self.top_left.y
&& point.y <= self.bottom_right.y
}
}
fn main() {
let p1 = Point::new(0, 0);
let p2 = Point::new(10, 10);
println!("点之间的距离: {:.2}", p1.distance(&p2));
let rect = Rectangle::new(p1, p2);
println!("矩形: {:?}", rect);
println!("宽度: {}", rect.width());
println!("高度: {}", rect.height());
println!("面积: {}", rect.area());
let p3 = Point::new(5, 5);
println!("点 {:?} 在矩形内: {}", p3, rect.contains(&p3));
}通过这些示例,我们可以看到结构体是如何组织和封装相关数据和行为的。结构体是 Rust 中构建复杂程序的基础工具之一。