Rust 普通结构体的设计思路
Rust 普通结构体的基本概念
在 Rust 中,结构体是一种自定义的数据类型,它允许将不同类型的数据组合在一起,形成一个有意义的整体。普通结构体是最常见的结构体类型,通过 struct
关键字来定义。例如,我们定义一个表示坐标点的结构体:
struct Point {
x: i32,
y: i32,
}
在上述代码中,Point
是结构体的名称,x
和 y
是结构体的字段,它们的类型都是 i32
。这种结构体定义方式类似于其他编程语言中的记录或复合数据类型。
结构体字段的类型选择
- 基本数据类型作为字段
- Rust 的基本数据类型,如整数(
i32
、u64
等)、浮点数(f32
、f64
)、布尔值(bool
)以及字符(char
),都可以作为结构体字段的类型。例如,定义一个表示书籍信息的结构体:
- Rust 的基本数据类型,如整数(
struct Book {
title: &str,
author: &str,
price: f32,
is_bestseller: bool,
}
这里 title
和 author
使用 &str
类型表示字符串切片,price
是 f32
类型的价格,is_bestseller
是 bool
类型表示是否是畅销书。
2. 自定义结构体作为字段
- 结构体的字段类型也可以是另一个自定义的结构体。比如,我们有一个表示地址的结构体 Address
,然后在表示用户信息的结构体 User
中使用它:
struct Address {
street: &str,
city: &str,
zip_code: u32,
}
struct User {
name: &str,
age: u8,
address: Address,
}
在 User
结构体中,address
字段的类型是 Address
结构体。这样就可以将复杂的数据结构分层组织,提高代码的可读性和可维护性。
3. 集合类型作为字段
- Rust 的集合类型,如 Vec<T>
(动态数组)、HashMap<K, V>
(哈希表)等,也能作为结构体字段。例如,定义一个表示班级学生信息的结构体,其中学生成绩用 Vec<u8>
存储:
struct Class {
class_name: &str,
students: Vec<&str>,
scores: Vec<u8>,
}
students
字段存储学生的名字,scores
字段存储对应的成绩。如果需要存储学生的详细信息,还可以使用 Vec<Student>
,其中 Student
是一个自定义结构体。
结构体实例的创建
- 通过字段初始化语法创建实例
- 可以使用字段初始化语法来创建结构体的实例。对于前面定义的
Point
结构体,可以这样创建实例:
- 可以使用字段初始化语法来创建结构体的实例。对于前面定义的
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 10, y: 20 };
println!("Point x: {}, y: {}", p1.x, p1.y);
}
在 main
函数中,通过 Point { x: 10, y: 20 }
这种方式创建了 Point
结构体的实例 p1
,并通过 .
运算符访问结构体的字段。
2. 使用 Default
特征创建实例
- 如果结构体实现了 Default
特征,就可以使用 Default::default()
方法来创建实例。首先,让 Point
结构体实现 Default
特征:
use std::default::Default;
struct Point {
x: i32,
y: i32,
}
impl Default for Point {
fn default() -> Self {
Point { x: 0, y: 0 }
}
}
fn main() {
let p2 = Point::default();
println!("Default Point x: {}, y: {}", p2.x, p2.y);
}
在上述代码中,通过实现 Default
特征的 default
方法,指定了 Point
结构体实例的默认值为 x = 0
,y = 0
。然后在 main
函数中使用 Point::default()
创建了默认值的 Point
实例 p2
。
3. 通过函数创建实例
- 可以定义函数来创建结构体实例,这样可以对输入进行验证或处理。例如,为 Book
结构体定义一个创建实例的函数:
struct Book {
title: &str,
author: &str,
price: f32,
is_bestseller: bool,
}
fn create_book(title: &str, author: &str, price: f32, is_bestseller: bool) -> Book {
if price < 0.0 {
panic!("Price cannot be negative");
}
Book {
title,
author,
price,
is_bestseller,
}
}
fn main() {
let book1 = create_book("Rust Programming", "Steve Klabnik", 39.9, false);
println!("Book: {}, by {}", book1.title, book1.author);
}
在 create_book
函数中,对 price
进行了检查,确保价格不能为负数。如果价格为负数,就会触发 panic!
宏,程序会终止并输出错误信息。
结构体的方法定义
- 实例方法
- 实例方法是与结构体实例相关联的函数。通过
impl
块来为结构体定义方法。例如,为Point
结构体定义一个计算到原点距离的方法:
- 实例方法是与结构体实例相关联的函数。通过
struct Point {
x: i32,
y: i32,
}
impl Point {
fn distance_to_origin(&self) -> f64 {
(self.x.pow(2) + self.y.pow(2)) as f64).sqrt()
}
}
fn main() {
let p = Point { x: 3, y: 4 };
let dist = p.distance_to_origin();
println!("Distance to origin: {}", dist);
}
在 impl Point
块中,定义了 distance_to_origin
方法。方法的第一个参数 &self
表示结构体实例的引用,这里使用 &
是因为我们不需要获取所有权,只需要读取结构体的字段。
2. 关联函数
- 关联函数是与结构体类型相关联的函数,而不是与结构体实例相关联。通过在 impl
块中定义不以 &self
为第一个参数的函数来创建关联函数。例如,为 Point
结构体定义一个创建原点的关联函数:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn origin() -> Point {
Point { x: 0, y: 0 }
}
}
fn main() {
let origin = Point::origin();
println!("Origin: x = {}, y = {}", origin.x, origin.y);
}
在上述代码中,origin
函数是 Point
结构体的关联函数,通过 Point::origin()
来调用,它返回一个表示原点的 Point
实例。
结构体的可变性
- 可变结构体实例
- 可以创建可变的结构体实例,以便修改其字段的值。例如,对于
Point
结构体:
- 可以创建可变的结构体实例,以便修改其字段的值。例如,对于
struct Point {
x: i32,
y: i32,
}
fn main() {
let mut p = Point { x: 10, y: 20 };
p.x = 15;
println!("Modified Point x: {}, y: {}", p.x, p.y);
}
在 main
函数中,通过 let mut p
创建了一个可变的 Point
实例 p
,然后可以使用 .
运算符修改 p
的 x
字段的值。
2. 方法中的可变性
- 在结构体的方法中,也可以通过 &mut self
参数来修改结构体实例的字段。例如,为 Point
结构体定义一个移动点的方法:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn move_point(&mut self, dx: i32, dy: i32) {
self.x += dx;
self.y += dy;
}
}
fn main() {
let mut p = Point { x: 10, y: 20 };
p.move_point(5, -3);
println!("Moved Point x: {}, y: {}", p.x, p.y);
}
在 move_point
方法中,&mut self
表示可以修改结构体实例 p
的字段。通过 self.x += dx
和 self.y += dy
来移动点的位置。
结构体与所有权和借用
- 所有权转移
- 当结构体实例作为函数参数传递时,所有权会发生转移。例如:
struct Book {
title: String,
author: String,
}
fn print_book(book: Book) {
println!("Book: {}, by {}", book.title, book.author);
}
fn main() {
let my_book = Book {
title: String::from("Rust in Action"),
author: String::from("Tim McNamara"),
};
print_book(my_book);
// 这里不能再使用 my_book,因为所有权已转移到 print_book 函数中
}
在 print_book
函数中,参数 book
获得了 my_book
的所有权。函数结束后,book
被销毁,my_book
在 main
函数中不能再被使用。
2. 借用结构体实例
- 为了避免所有权转移,可以借用结构体实例。例如,定义一个只读取 Book
信息的函数:
struct Book {
title: String,
author: String,
}
fn print_book_info(book: &Book) {
println!("Book: {}, by {}", book.title, book.author);
}
fn main() {
let my_book = Book {
title: String::from("Rust in Action"),
author: String::from("Tim McNamara"),
};
print_book_info(&my_book);
// 这里可以继续使用 my_book,因为只是借用
}
在 print_book_info
函数中,参数 book
是 &Book
类型,即对 Book
结构体实例的借用。这样 my_book
的所有权仍然在 main
函数中,函数结束后可以继续使用 my_book
。
3. 可变借用
- 如果需要在函数中修改结构体实例,可以使用可变借用。例如,为 Book
结构体定义一个修改标题的函数:
struct Book {
title: String,
author: String,
}
fn change_book_title(book: &mut Book, new_title: &str) {
book.title = String::from(new_title);
}
fn main() {
let mut my_book = Book {
title: String::from("Rust in Action"),
author: String::from("Tim McNamara"),
};
change_book_title(&mut my_book, "New Rust Book");
println!("Book: {}, by {}", my_book.title, my_book.author);
}
在 change_book_title
函数中,参数 book
是 &mut Book
类型,即对 Book
结构体实例的可变借用。这样可以在函数中修改 book
的 title
字段。
结构体的内存布局
- 内存对齐
- Rust 结构体的内存布局遵循内存对齐原则。内存对齐是为了提高内存访问效率,让数据在内存中按照特定的边界对齐。例如,对于
Point
结构体:
- Rust 结构体的内存布局遵循内存对齐原则。内存对齐是为了提高内存访问效率,让数据在内存中按照特定的边界对齐。例如,对于
struct Point {
x: i32,
y: i32,
}
i32
类型通常占用 4 个字节,并且在内存中以 4 字节边界对齐。所以 Point
结构体实例在内存中占用 8 个字节,并且其起始地址是 4 的倍数。
2. 结构体嵌套的内存布局
- 当结构体中包含其他结构体作为字段时,内存布局会根据嵌套结构体的布局和对齐规则进行。例如:
struct Inner {
a: u8,
b: u16,
}
struct Outer {
inner: Inner,
c: u32,
}
Inner
结构体中,a
占用 1 个字节,b
占用 2 个字节,由于 b
以 2 字节对齐,所以 Inner
结构体占用 4 个字节(1 + 1(填充)+ 2)。Outer
结构体中,inner
占用 4 个字节,c
占用 4 个字节,Outer
结构体总共占用 8 个字节,其起始地址是 4 的倍数。
结构体的序列化与反序列化
- 使用 Serde 库进行序列化
- Serde 是一个常用的 Rust 库,用于序列化和反序列化数据。首先,在
Cargo.toml
文件中添加依赖:
- Serde 是一个常用的 Rust 库,用于序列化和反序列化数据。首先,在
[dependencies]
serde = "1.0"
serde_json = "1.0"
然后,为 Book
结构体添加 Serialize
特征:
use serde::{Serialize};
struct Book {
title: &str,
author: &str,
price: f32,
is_bestseller: bool,
}
impl Serialize for Book {}
fn main() {
let book = Book {
title: "Rust Programming",
author: "Steve Klabnik",
price: 39.9,
is_bestseller: false,
};
let json = serde_json::to_string(&book).unwrap();
println!("Serialized JSON: {}", json);
}
在上述代码中,通过 serde_json::to_string
函数将 Book
结构体实例序列化为 JSON 字符串。
2. 使用 Serde 库进行反序列化
- 同样使用 Serde 库进行反序列化。为 Book
结构体添加 Deserialize
特征:
use serde::{Deserialize, Serialize};
struct Book {
title: &str,
author: &str,
price: f32,
is_bestseller: bool,
}
impl Serialize for Book {}
impl Deserialize for Book {}
fn main() {
let json = r#"{"title":"Rust Programming","author":"Steve Klabnik","price":39.9,"is_bestseller":false}"#;
let book: Book = serde_json::from_str(json).unwrap();
println!("Deserialized Book: {}, by {}", book.title, book.author);
}
在 main
函数中,通过 serde_json::from_str
函数将 JSON 字符串反序列化为 Book
结构体实例。
结构体设计的最佳实践
- 单一职责原则
- 结构体应该遵循单一职责原则,即一个结构体应该只有一个明确的职责。例如,
Point
结构体只负责表示坐标点,不应该包含与坐标点无关的功能。如果需要其他功能,应该创建新的结构体或使用其他方式来实现。
- 结构体应该遵循单一职责原则,即一个结构体应该只有一个明确的职责。例如,
- 字段命名规范
- 结构体字段的命名应该清晰、有意义,遵循 Rust 的命名规范(通常使用蛇形命名法)。例如,
book_title
比bt
更易读和理解。
- 结构体字段的命名应该清晰、有意义,遵循 Rust 的命名规范(通常使用蛇形命名法)。例如,
- 合理使用所有权和借用
- 在设计结构体和相关函数时,要合理考虑所有权和借用。尽量减少不必要的所有权转移,通过借用提高代码的效率和安全性。同时,要注意可变借用的规则,避免数据竞争。
- 文档化结构体和方法
- 使用 Rust 的文档注释(
///
)为结构体和其方法添加文档。这样可以提高代码的可读性和可维护性,方便其他开发者理解和使用你的代码。例如:
- 使用 Rust 的文档注释(
/// Represents a book.
///
/// This struct contains information about a book, such as its title, author, price, and whether it is a bestseller.
struct Book {
/// The title of the book.
title: &str,
/// The author of the book.
author: &str,
/// The price of the book.
price: f32,
/// Whether the book is a bestseller.
is_bestseller: bool,
}
/// Create a new book instance.
///
/// # Arguments
///
/// * `title` - The title of the book.
/// * `author` - The author of the book.
/// * `price` - The price of the book.
/// * `is_bestseller` - Whether the book is a bestseller.
///
/// # Returns
///
/// A new `Book` instance.
fn create_book(title: &str, author: &str, price: f32, is_bestseller: bool) -> Book {
if price < 0.0 {
panic!("Price cannot be negative");
}
Book {
title,
author,
price,
is_bestseller,
}
}
通过这些文档注释,其他开发者可以清楚地了解 Book
结构体的用途以及 create_book
函数的参数和返回值。
通过以上对 Rust 普通结构体设计思路的详细阐述,包括基本概念、字段类型选择、实例创建、方法定义、可变性、所有权与借用、内存布局、序列化反序列化以及最佳实践等方面,希望能帮助开发者更好地设计和使用 Rust 中的普通结构体,编写出高效、安全且易于维护的代码。