Skip to content

09.Struct

结构体的命名规则一般遵循 PascalCase 模式。

PascalCase 模式:各单词首字母大写,单词之间没有空格或下划线。

结构体的定义

rust
struct User {
    email: String,
    username: String,
    active: bool,
    sign_in_count: u64,
}

每个字段包含一个名称类型。只有当结构体被声明后,后续才能够使用该结构体,使用结构体的例子如下:

rust
let mut user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("admin123"),
    active: true,
    sign_in_count: 1,
};

可以直接修改结构体中某字段的值(前提是定义的结构体是可变类型),使用点式访问法访问结构体中的字段:

rust
user1.email = String::from("someone@outlook.com");

结构体的应用

函数返回结构体

写一个函数,返回的值是结构体:

rust
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

注意,当原本的结构体的字段名称和值(来自函数的参数)的名称相同时,可以直接写函数的参数:

image-20250225092644857

如果除了某个字段(如 email)外,其他字段都和某个结构体相同,则以下是简便的声明方法:

rust
let mut user2 = User {
    email: String::from("someone@123.com"),
    ..user1 // user1 的 email 字段被 user2 覆盖
};

如果使用上述的声明方法,则:

  • 使用 ..user1user1 的字段的所有权会被转移给 user2(除非实现了 Copy trait)。
  • 转移是完全的user1 现在不能再访问这些字段。
  • 对于 email 字段,由于在 user2 中提前定义了 email 字段,因此在执行 ..user1 时,user1email 字段的所有权不会被转移,后续仍可通过 user1.email 访问 user1email 字段。

结构体的借用

案例:

rust
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let mut p = Point { x: 1, y: 2, z: 3 };
    let x = &mut p.x;  // 执行此行完毕后,p 失去所有权限(包括读)
    // print_point(&p);  // 此处传入的是不可变引用,rust 不允许变量同时拥有可变和不可变引用
    // 此时 p 中元素已被可变借用,因此 p 不可再被借用,即使是可变借用也不行
    p.y = 3;  // 但 p 中的其他元素可以被访问,因为只存在对 x 的借用,不会影响其他元素
    *x += 1;
    print_point(&p);  // 所有借用使用完毕,可以对 p 进行借用
}

fn print_point(p: &Point) {
    println!("{}, {}, {}", p.x, p.y, p.z);
}

输出:

2, 3, 3

Tuple Struct

元组结构体,字段只有类型,没有名称,声明如下:

rust
struct Color(i32, i32, i32);

let black = Color(0, 0, 0);

打印结构体

打印结构体需要对结构体开启 debug 模式,然后占位符使用 :? ,如下:

rust
#[derive(Debug)]
struct User {
    email: String,
    username: String,
    active: bool,
    sign_in_count: u64,
}

println!("结构体 user2:\n{:?}", user2);
println!("美化结构体打印 user2:\n{user2:#?}");  // 在 : 和 ? 之间加上 #

输出:

结构体 user2:
User { email: "someone@123.com", username: "admin123", active: true, sign_in_count: 1 }
美化结构体打印 user2:
User {
    email: "someone@123.com",
    username: "admin123",
    active: true,
    sign_in_count: 1,
}

重点习题讲解

习题 1

以下代码能否通过编译?

rust
struct Point3 {
    x: i32,
    y: i32,
}
impl Point3 {
    fn get_x(&mut self) -> &mut i32 {
        &mut self.x
    }
}

let mut p2 = Point3 { x: 1, y: 2 };
let x = p2.get_x();
*x += 1;
println!("{} {}", *x, p2.y);

解答:

不能,因为当 x 通过 get_x() 方法获得 p2 中的 x 的可变借用时,由于 rust 的安全机制,其借用规则认为整个 p2 都被借用了(即使实际上只借用了其中的一个字段),因此在可变借用 x 的生命周期内,也不能对 p2 的其他部分(比如 p2.y)进行任何访问。

后续的 println! 中使用到了 x ,说明可变借用 x 的作用域还未结束,则此时也不能访问 p2 中的其他元素。