Rust 结构体(struct)

一、结构体概述

结构体(struct)是 Rust 中的核心复合数据结构,用于将多个不同类型的数据组合成一个逻辑整体,实现对现实事物或抽象概念的建模。它与元组的区别在于:

  • 元组仅通过位置区分元素,无明确名称,访问依赖索引。
  • 结构体为每个字段定义专属名称,无需依赖字段顺序访问,灵活性和可读性更强。

其他编程语言中类似概念包括:JavaScript 的 object、C/C++ 的 struct、Python 的 class(简化数据存储场景)等。

二、结构体基础语法

2.1 定义结构体

结构体定义需包含三个核心部分:关键字 struct、结构体名称、若干带类型的字段。

语法格式
rust 复制代码
struct 结构体名称 {
    字段1: 数据类型1,
    字段2: 数据类型2,
    // ... 更多字段
}
示例:定义用户结构体
rust 复制代码
#![allow(unused)] // 允许未使用的变量/结构体,避免编译警告
fn main() {
    // 定义存储网站用户信息的结构体
    struct User {
        active: bool,       // 账号是否激活(布尔型)
        username: String,   // 用户名(字符串类型,拥有所有权)
        email: String,      // 邮箱(字符串类型,拥有所有权)
        sign_in_count: u64  // 登录次数(无符号64位整数)
    }
}

2.2 创建结构体实例

创建实例时需为所有字段初始化,字段顺序可与定义时不同。

语法格式
rust 复制代码
let 实例名称 = 结构体名称 {
    字段1: 初始值1,
    字段2: 初始值2,
    // ... 所有字段需完整初始化
};
示例:创建 User 实例
rust 复制代码
#![allow(unused)]
fn main() {
    struct User {
        active: bool,
        username: String,
        email: String,
        sign_in_count: u64
    }

    // 创建用户实例,字段顺序与定义时不同(合法)
    let user1 = User {
        email: String::from("someone@example.com"), // 邮箱初始化
        username: String::from("someusername123"),  // 用户名初始化
        active: true,                               // 激活状态初始化
        sign_in_count: 1                            // 登录次数初始化
    };
}

2.3 访问与修改结构体字段

通过 . 操作符 访问结构体字段;若需修改字段值,需将实例声明为 mut(Rust 不支持单独标记某个字段为可变)。

示例:访问与修改字段
rust 复制代码
#![allow(unused)]
fn main() {
    struct User {
        active: bool,
        username: String,
        email: String,
        sign_in_count: u64
    }

    // 声明可变实例
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1
    };

    // 访问 email 字段并修改值
    user1.email = String::from("anotheremail@example.com");
    println!("修改后的邮箱:{}", user1.email); // 输出:修改后的邮箱:anotheremail@example.com
}

三、结构体创建简化技巧

3.1 字段同名简化初始化

当函数参数与结构体字段名称相同时,可省略"字段名: 参数名"的重复写法,直接使用字段名初始化。

示例:简化构建函数
rust 复制代码
#![allow(unused)]
fn main() {
    struct User {
        active: bool,
        username: String,
        email: String,
        sign_in_count: u64
    }

    // 未简化的构建函数:email: email 和 username: username 重复
    fn build_user_original(email: String, username: String) -> User {
        User {
            email: email,
            username: username,
            active: true,
            sign_in_count: 1
        }
    }

    // 简化后的构建函数:同名字段直接省略赋值符号
    fn build_user_simplified(email: String, username: String) -> User {
        User {
            email,  // 等价于 email: email
            username, // 等价于 username: username
            active: true,
            sign_in_count: 1
        }
    }

    // 调用简化构建函数
    let user2 = build_user_simplified(
        String::from("user2@example.com"),
        String::from("user2")
    );
}

3.2 结构体更新语法

基于已有结构体实例创建新实例时,可使用 ..已有实例 语法自动复用未显式修改的字段,避免重复赋值。

语法格式
rust 复制代码
let 新实例 = 结构体名称 {
    需修改的字段: 新值,
    ..已有实例 // 复用已有实例的其他字段(必须放在最后)
};
示例:更新结构体实例
rust 复制代码
#![allow(unused)]
fn main() {
    struct User {
        active: bool,
        username: String,
        email: String,
        sign_in_count: u64
    }

    // 已有实例 user1
    let user1 = User {
        email: String::from("user1@example.com"),
        username: String::from("user1"),
        active: true,
        sign_in_count: 5
    };

    // 基于 user1 创建 user3,仅修改 email 字段
    let user3 = User {
        email: String::from("user3@example.com"), // 仅修改邮箱
        ..user1 // 复用 user1 的 active、username、sign_in_count 字段
    };
}
注意:所有权转移与拷贝

更新语法中,字段的所有权行为取决于其类型是否实现 Copy 特征:

  • 实现 Copy 的类型(如 boolu64i32):仅拷贝数据,原有实例的字段仍可使用。
  • 未实现 Copy 的类型(如 StringVec<T>):发生所有权转移,原有实例的该字段不可再使用。

示例验证:

rust 复制代码
#[derive(Debug)] // 用于打印结构体(后续章节讲解)
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64
}

fn main() {
    let user1 = User {
        email: String::from("user1@example.com"),
        username: String::from("user1"),
        active: true,
        sign_in_count: 5
    };

    let user3 = User {
        email: String::from("user3@example.com"),
        ..user1
    };

    // 合法:active 是 bool 类型(实现 Copy),user1 的 active 未转移
    println!("user1 的激活状态:{}", user1.active);
    
    // 报错:username 是 String 类型(未实现 Copy),所有权已转移到 user3
    // println!("user1 的用户名:{}", user1.username);
    
    // 报错:user1 的 username 已转移,整个 user1 不可再使用
    // println!("user1: {:?}", user1);
}

四、特殊结构体类型

4.1 元组结构体(Tuple Struct)

元组结构体是结构体与元组的结合:有结构体名称,但字段无名称,仅通过位置区分。适用于需要整体标识、但字段含义明确无需命名的场景(如坐标、颜色)。

语法格式
rust 复制代码
struct 结构体名称(字段类型1, 字段类型2, ...);
示例:定义与使用元组结构体
rust 复制代码
#![allow(unused)]
fn main() {
    // 定义元组结构体:Color(存储 RGB 颜色值)、Point(存储 3D 坐标)
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    // 创建实例:无需指定字段名,按位置传值
    let black = Color(0, 0, 0);   // 黑色(RGB:0,0,0)
    let origin = Point(0, 0, 0);  // 3D 坐标系原点(x=0,y=0,z=0)

    // 访问字段:通过索引(类似元组)
    println!("黑色的 R 通道值:{}", black.0); // 输出:0
    println!("原点的 z 坐标:{}", origin.2); // 输出:0
}
注意:不同元组结构体不可混用

即使字段类型完全相同,不同名称的元组结构体仍属于不同类型,不可相互赋值或比较:

rust 复制代码
#![allow(unused)]
fn main() {
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

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

    // 报错:Color 和 Point 是不同类型
    // let wrong: Color = origin;
}

4.2 单元结构体(Unit-like Struct)

单元结构体无任何字段,类似 Rust 中的单元类型 ()。适用于仅需类型标识、无需存储数据的场景(如实现特定特征、标记类型)。

语法格式
rust 复制代码
struct 结构体名称; // 无字段,无括号
示例:定义与使用单元结构体
rust 复制代码
#![allow(unused)]
fn main() {
    // 定义单元结构体:表示"始终相等"的标记类型
    struct AlwaysEqual;

    // 创建实例:无需初始化字段
    let subject = AlwaysEqual;

    // 场景:为单元结构体实现特征(后续章节讲解特征时会深入)
    trait Equal {
        fn is_equal(&self, other: &Self) -> bool;
    }

    // 实现 Equal 特征:任何 AlwaysEqual 实例都相等
    impl Equal for AlwaysEqual {
        fn is_equal(&self, other: &Self) -> bool {
            true // 始终返回 true
        }
    }

    // 使用特征方法
    let a = AlwaysEqual;
    let b = AlwaysEqual;
    println!("a 和 b 是否相等:{}", a.is_equal(&b)); // 输出:true
}

五、结构体的所有权管理

结构体字段的所有权决定了数据的生命周期和访问权限。默认情况下,结构体应存储拥有所有权的数据 (如 String),而非引用(如 &str),避免生命周期问题。

5.1 存储拥有所有权的数据(推荐)

使用 StringVec<T> 等拥有所有权的类型作为字段时,结构体完全控制数据的生命周期,无需额外处理借用规则。

示例:正确的所有权设计
rust 复制代码
#![allow(unused)]
fn main() {
    struct User {
        username: String, // 拥有所有权,结构体生命周期独立
        email: String,
        sign_in_count: u64
    }

    let user = User {
        username: String::from("owner_user"),
        email: String::from("owner@example.com"),
        sign_in_count: 3
    };
    // 合法:结构体拥有数据所有权,可正常使用
    println!("用户名:{}", user.username);
}

5.2 存储引用(需生命周期)

若结构体字段为引用(如 &str),必须通过生命周期(lifetimes) 明确引用的有效范围,否则编译器报错。生命周期的核心作用是确保:结构体的生命周期 ≤ 其引用字段的生命周期。

错误示例:未指定生命周期
rust 复制代码
// 报错:引用字段缺少生命周期标识
struct User {
    username: &str, // 错误:expected named lifetime parameter
    email: &str,    // 错误:expected named lifetime parameter
    sign_in_count: u64
}

fn main() {
    let user = User {
        username: "error_user",
        email: "error@example.com",
        sign_in_count: 1
    };
}
正确示例:添加生命周期(后续章节详解)
rust 复制代码
#![allow(unused)]
fn main() {
    // 添加生命周期 'a,指定引用字段的生命周期
    struct User<'a> {
        username: &'a str, // 引用的生命周期为 'a
        email: &'a str,    // 引用的生命周期为 'a
        sign_in_count: u64
    }

    // 定义字符串字面量(静态生命周期,满足 'a 要求)
    let username = "valid_user";
    let email = "valid@example.com";

    // 创建实例:引用的生命周期 ≥ 结构体生命周期
    let user = User {
        username,
        email,
        sign_in_count: 2
    };
}

注:生命周期是 Rust 的进阶概念,建议先掌握结构体基础用法,再深入学习。

六、结构体的调试与打印

Rust 默认不支持直接打印结构体(如 println!("{}", struct_instance)),需通过 Debug 特征实现调试打印,或手动实现 Display 特征实现自定义打印。

6.1 使用 #[derive(Debug)] 派生 Debug 特征

Debug 特征用于调试场景,可通过 {:?}{:#?} 格式符打印结构体。#[derive(Debug)] 是 Rust 提供的自动实现 Debug 特征的宏。

示例:基础 Debug 打印
rust 复制代码
// 派生 Debug 特征,允许调试打印
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50
    };

    // 使用 {:?} 打印(紧凑格式)
    println!("Rectangle 紧凑格式:{:?}", rect);
    // 输出:Rectangle 紧凑格式:Rectangle { width: 30, height: 50 }

    // 使用 {:#?} 打印(格式化格式,适合复杂结构体)
    println!("Rectangle 格式化格式:{:#?}", rect);
    // 输出:
    // Rectangle 格式化格式:Rectangle {
    //     width: 30,
    //     height: 50
    // }
}

6.2 使用 dbg! 宏增强调试

dbg! 宏是更强大的调试工具,具有以下特性:

  • 打印文件名、行号、表达式及表达式的值。
  • 输出到标准错误流(stderr),而非标准输出流(stdout),避免与正常输出混淆。
  • 会返回表达式的所有权,不影响后续代码使用。
示例:使用 dbg! 调试
rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32
}

fn main() {
    let scale = 2;
    // 使用 dbg! 打印表达式 30 * scale 的值,并返回结果赋值给 width
    let rect = Rectangle {
        width: dbg!(30 * scale), // 调试打印:[src/main.rs:10] 30 * scale = 60
        height: 50
    };

    // 使用 dbg! 打印 rect 的引用(避免所有权转移)
    dbg!(&rect); // 调试打印:[src/main.rs:14] &rect = Rectangle { width: 60, height: 50 }
}
输出结果
复制代码
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect = Rectangle {
    width: 60,
    height: 50,
}

6.3 手动实现 Display 特征(自定义打印)

若需更友好的用户级打印格式(如无结构体名称、自定义字段顺序),可手动实现 Display 特征,使用 {} 格式符打印。

示例:实现 Display 特征
rust 复制代码
use std::fmt; // 导入 fmt 模块,用于实现 Display

// 定义矩形结构体
struct Rectangle {
    width: u32,
    height: u32
}

// 手动实现 Display 特征
impl fmt::Display for Rectangle {
    // f: &mut fmt::Formatter 是格式化输出的写入对象
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // 自定义输出格式:"矩形:宽 30,高 50"
        write!(f, "矩形:宽 {}, 高 {}", self.width, self.height)
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50
    };

    // 使用 {} 打印(Display 特征)
    println!("{}", rect); // 输出:矩形:宽 30,高 50
}

七、结构体的内存排列

结构体在内存中是连续存储的,但字段的具体排列受内存对齐 影响(Rust 会自动优化内存布局,减少内存碎片)。以 File 结构体为例:

7.1 示例结构体定义

rust 复制代码
#[derive(Debug)]
struct File {
    name: String,  // 字符串类型(底层包含 ptr、len、capacity)
    data: Vec<u8>  // 字节向量(底层包含 ptr、len、capacity)
}

7.2 内存布局解析

字段 底层组成 内存含义
name ptr(指针) 指向字符串底层 [u8] 数组的内存地址
len( usize) 字符串的长度(字节数)
capacity(usize) 字符串分配的内存容量(字节数,≥ len)
data ptr(指针) 指向向量底层 [u8] 数组的内存地址
len( usize) 向量中元素的个数(字节数)
capacity(usize) 向量分配的内存容量(元素个数,≥ len)

7.3 核心结论

  • 结构体字段本身存储在连续内存区域,但字段指向的数据(如 StringVec<T> 的底层数组)存储在堆上,通过指针关联。
  • 结构体拥有其所有字段的所有权,若字段指向堆数据(如 String),结构体销毁时会自动释放堆内存(避免内存泄漏)。

八、课后练习与拓展

  1. 基础练习 :定义一个 Book 结构体,包含 title(书名,String)、author(作者,String)、pages(页数,u32)、published(是否出版,bool),并创建 2 个实例,使用更新语法基于第一个实例创建第二个实例(修改书名和作者)。

  2. 进阶练习 :为 Book 结构体派生 Debug 特征,使用 dbg! 宏调试打印实例;再手动实现 Display 特征,自定义输出格式(如"《书名》- 作者,共 X 页,出版状态:是/否")。

  3. 拓展学习 :尝试定义一个元组结构体 Point2D(存储 2D 坐标,f64, f64),实现一个方法计算两个点之间的距离(使用勾股定理:√[(x2-x1)² + (y2-y1)²])。

九、总结

结构体是 Rust 中实现数据抽象和封装的核心工具,主要特性包括:

  1. 灵活性:支持命名字段,无需依赖字段顺序访问。
  2. 简化语法:字段同名初始化、更新语法减少重复代码。
  3. 特殊类型:元组结构体适合简单标识场景,单元结构体适合类型标记场景。
  4. 所有权安全:默认存储拥有所有权的数据,引用需配合生命周期确保安全。
  5. 调试友好 :通过 #[derive(Debug)]dbg! 宏快速实现调试打印。
相关推荐
liuxuzxx2 小时前
使用Rust构建MCP Server Stdio类型
rust·mcp
努力写代码的熊大2 小时前
深入探索C++关联容器:Set、Map、Multiset与Multimap的终极指南及底层实现剖析
开发语言·c++
枫叶梨花2 小时前
SpringBoot+Vue实现SM4加密传输
spring boot·后端
悟空码字2 小时前
SpringBoot整合MyBatis-Flex保姆级教程,看完就能上手!
java·spring boot·后端
J_liaty2 小时前
Java工程师的JVM入门教程:从零理解Java虚拟机
java·开发语言·jvm
kklovecode2 小时前
C语言之头文件,宏和条件编译
c语言·开发语言·算法
txinyu的博客2 小时前
Linux 内存管理
linux·运维·开发语言·c++
qq_2500568682 小时前
SpringBoot 引入 smart-doc 接口文档插件
java·spring boot·后端
m0_748252382 小时前
Ruby 数据类型概述
开发语言·mysql·ruby