struct 结构体
自定义数据类型,一种数据的组织方式
struct实例化必须每个字段都赋值- 每个字段拥有独立的所有权
- 更新语法必须在新结构体的后面
- 更新语法会转移结构体字段的所有权
- 旧的结构体中的字段必须被新的结构体包含,不能有新结构体没有的字段
- 部分移动规则: 非
Copy字段被移动后,整个结构体实例进入不完整状态,可以访问拥有所有权的字段,但是结构体示例本身不能再整体使用(如赋值给另一个变量,或者作为整体传递给函数等) - 结构体分为三种: 经典结构体、元组结构体、类单元结构体
- 方法通过
.调用,关联函数通过::调用
定义struct
- 结构体名称应使用帕斯卡命名法(PascalCase)
- 字段名称应使用蛇形命名法(snake_case)
- 字段类型必须明确指定
- 结构体定义本身不占用内存,只有实例化后才占用内存
经典结构体 classic structs
rust
struct User {
name: String,
age: i32,
email: String,
}元组结构体 tuple structs
rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);black 和 origin 是不同的类型,哪怕里面的类型都是一样的
类单元结构体 unit-like structs
没有任何字段的结构体
rust
struct AlwaysEqual;实例化struct
rust
// 1. 实例化经典结构体
let san = User {
name: String::from("zhangsan"),
age: 13,
email: String::from("123@gmail.com")
};
// 2. 实例化元组结构体
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// 3. 实例化类单元结构体
let subject = AlwaysEqual;实例化要点
- 实例化时所有字段都必须被赋值
- 字段的赋值顺序可以与定义顺序不同
- 字段初始化简写: 当变量名与字段名相同时,可以省略字段名(如
User { name, age, email }) - 结构体实例必须是可变的(加
mut)才能修改其字段 - 只有经典结构体支持字段级别的初始化
访问
使用点语法
rust
// 1. 访问经典结构体字段
let san_name = san.name; // ✅ 获取字段值
// 2. 访问元组结构体字段
let black0 = black.0; // ✅ 通过索引访问
// 3. 类单元结构体没有字段,无法访问访问要点
- 只有拥有所有权或获得借用的实例才能访问字段
- 访问不可变引用的字段不会转移所有权
- 访问
Copy类型的字段会复制值,不影响原实例 - 访问
String等非Copy类型会转移所有权(在更新语法中需注意) - 私有字段无法从模块外访问(所有权和可见性是两个独立的概念)
修改
使用点语法直接修改字段值
rust
// 1. 修改经典结构体字段
san.name = "lisi".to_string(); // ✅ 必须是 mut 实例才能修改
// 2. 修改元组结构体字段
black.0 = 255; // ✅ 必须是 mut 实例才能修改
// 3. 类单元结构体没有字段,无法修改修改要点
- 实例必须声明为
mut(可变),才能修改其字段 - 修改时只需要
mut权限,不需要所有权转移 - 修改
String等非Copy类型字段不影响其他字段的访问 - 不能只修改结构体中的某个字段为可变,必须整个实例为可变
- 修改字段后原实例的所有权和状态保持不变
更新语法
修改使用同一个结构体模板的变量的其中某部分值
rust
let new_san = User{
email: String::from("123@gmail.com"),
..san
}更新语法要点
san必须是已经通过User实例化的结构体- 新的
new_san变量复用san中除email外的其他字段值 - 更新语法会转移所有权,是字段级别的所有权转移
- 由于
struct中的所有权是单独的,实现了Copy的字段在更新语法后依旧可以直接访问 - 只能在新结构体后面使用
..语法(..san不能在前面) - 部分移动规则: 一旦非
Copy字段被移动,原变量进入"不完整"状态,可以访问拥有所有权的字段,但是结构体示例本身不能再整体使用(如赋值给另一个变量,或者作为整体传递给函数等)
部分移动(Partial Move): 结构体中有非
Copy字段(如String)被移动走后,Rust 编译器认为原变量处于"不完整"状态,允许访问仍然拥有所有权的字段(如age),但禁止对整个结构体实例进行任何操作(如赋值、传参等),以防止访问已被移动走的字段导致悬垂引用或数据不一致问题.
impl Struct
- 使用
impl关键字定义 - 一个结构体可以使用多个
impl定义结构体方法和关联函数
rust
impl Struct {
├── fn xxx(&self) → 方法(method)
├── fn xxx() → 关联函数(associated function)
├── const XXX → 关联常量
└── type XXX → 关联类型
}方法
impl Struct中,函数的第一个参数可以是self、&self、&mut self,或者没有self参数(静态方法/关联函数)&self是self: &Self的缩写self(小写): 指代具体的实例Self(大写): 指代这个类型本身
rust
impl User {
// 1. self 作为参数,获取实例的所有权
fn play(self) {
println!("{}正在玩", self.name);
}
// 2. &self 作为参数,获取实例的不可变借用
fn say(&self) {
println!("我的名字是{}", self.name);
}
// 3. &mut self 作为参数,获取实例的可变借用
fn update_name(&mut self, new_name: String) {
self.name = new_name;
}
}
fn main() {
let mut san = User {
name: String::from("zhangSan"),
age: 18,
email: "123@gmail.com".to_string(),
};
san.say(); // ✅ 调用方法
san.update_name("liSi".to_string()); // ✅ 调用方法修改名字
san.play(); // ✅ 调用方法获取所有权,之后 san 不能再使用
}关联函数
impl Struct中,没有self参数的函数叫做关联函数,通常用来定义构造函数- 通过
::调用,类似面向对象中的静态方法
rust
impl User {
fn new(name: String, age: u8, email: String) -> Self {
Self { name, age, email }
}
}
fn main() {
// 使用 :: 调用关联函数
let san = User::new("zhangSan".to_string(), 18, "123@gmail.com".to_string());
}为什么叫"关联函数"?
因为它不属于某个实例但属于这个类型,是关联在某个类型名下的函数.
综合例子
rust
// 定义结构体
#[derive(Debug)] // 实现Debug trait,可以使用println!宏打印
struct User {
name: String,
age: u8,
email: String,
}
// 定义结构体方法/关联函数
impl User {
// 关联函数,类似构造函数/静态方法
fn new(name: String, age: u8, email: String) -> Self {
Self { name, age, email }
}
// 方法
fn say(&self) {
println!("我的名字是{}", self.name);
}
}
fn main() {
// 实例化结构体
let mut san = User {
name: String::from("zhangSan"),
age: 18,
email: "345@gmail.com".to_string(),
};
// 更新语法
let mut si = User {
email: "1223@gmail.com".to_string(),
..san // san.name 和 san.email(String类型)所有权转移给 si
};
dbg!(san.age); // ✅ 可以访问,虽然部分移动,原变量 san 处于"不完整"状态,但 age 字段是 Copy 类型(拥有所有权),仍然可以访问
dbg!(si.age); // ✅ 可以访问,因为 si.age 是新结构体的字段,拥有所有权
// 修改san
san.name = "liSi".to_string(); // 重新赋值,拥有所有权
dbg!(san.name); // 这里能单独访问san.name,是因为san.name的所有权已经被重新赋值了,并且san.name是一个独立的字段,不受部分移动规则的限制,变相导致 san这个结构体又变成了完整的状态,所以可以访问san.name了,如果重新赋值没有补齐 san 结构体的完整性,那san.name依旧无法访问
// 使用say方法
si.say();
// 使用new函数,构造新的字段
let wu = User::new("wu".to_string(), 25, "567@gmail.com".to_string());
println!("{wu:#?}")
}