目录
- 序言
- 结构体(Struct)
-
- 结构体的定义
- 结构体的实例化
-
- 实例化规则------字段必须完全初始化
- 实例化规则------可变性
- 实例化规则------所有权规则
- [派生规则:#[derive] 的使用限制](#[derive] 的使用限制)
- 可变结构体
- [元组结构体(Tuple Struct)](#元组结构体(Tuple Struct))
- [单元结构体(Unit Struct)](#单元结构体(Unit Struct))
- 嵌套元组结构体
- 泛型结构体
- [结构体更新语法(Struct Update Syntax)](#结构体更新语法(Struct Update Syntax))
-
- 普通结构体(命名字段)
- 元组结构体(无名字段)
- 嵌套结构体
- [与 Default trait 结合(默认值复用)](#与 Default trait 结合(默认值复用))
- 总结
序言
前面有写过Rust语言入门的内容:Rust语言入门
本篇接着前面的内容进行进阶学习。其实就是通过官方文档(doc_rust-lang.or)去学习归纳总结。
结构体(Struct)
在 Rust 中,结构体(struct)是一种自定义数据类型,用于封装多个相关字段。结构体通过 struct 关键字定义,字段需明确指定名称和类型。比如下面这个demo,
rust
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
上面示例结果如下,

其中struct定义了一个Rectangle的结构体,内部包含名称和类型的字段属性,
rust
struct Rectangle {
width: u32,
height: u32,
}
结构体的定义
命名规范
结构体名必须用 PascalCase(首字母大写),字段名用 snake_case(小写 + 下划线),符合 Rust 社区惯例:
rust
// 正确:结构体 PascalCase,字段 snake_case
struct UserProfile {
user_name: String,
age: u8,
}
// 错误:结构体首字母小写(编译不报错,但不符合规范)
struct user_profile { /* ... */ }
语法格式
- 普通结构体:
struct 名称 { 字段1: 类型1, 字段2: 类型2, ... }(字段需显式指定类型)。 - 元组结构体:
struct 名称(类型1, 类型2, ...)(无字段名,仅存类型)。 - 单元结构体:
struct 名称;(无字段,仅作标记)。
可见性控制
- 结构体默认
私有(private):仅当前模块可见,跨模块访问需加pub。 - 字段默认也私有:即使结构体
pub,字段仍需单独加pub才能被外部访问:
rust
// 模块内定义结构体
mod my_module {
// 公开结构体(外部可访问)
pub struct Person {
pub name: String, // 公开字段(外部可读写)
age: u8, // 私有字段(仅模块内访问)
}
// 模块内可访问私有字段
pub fn new_person(name: String, age: u8) -> Person {
Person { name, age }
}
}
fn main() {
let p = my_module::new_person("Alice".to_string(), 22);
println!("Name: {}", p.name); // 合法:name 是公开字段
// println!("Age: {}", p.age); // 错误:age 是私有字段,外部不可访问
}
结构体的实例化
实例化规则------字段必须完全初始化
实例化时需为所有字段赋值(无默认值的情况下),不能遗漏:
rust
struct Point { x: i32, y: i32 }
// 正确:所有字段都赋值
let p1 = Point { x: 10, y: 20 };
// 错误:遗漏 y 字段
// let p2 = Point { x: 30 };
实例化规则------可变性
- 结构体实例默认不可变,需加
mut关键字才能修改字段(整体可变,不能单独标记某个字段为mut)。 - 若需 "部分字段可变",可将可变字段封装为内部结构体,或使用 Cell/RefCell(内部可变性)
rust
// 内部可变性(部分字段可变)
use std::cell::Cell;
struct Counter {
count: Cell<u32>, // 可变性独立于结构体实例
name: String, // 不可变字段
}
struct Point { x: i32, y: i32 }
fn main() {
// 整体可变
let mut p = Point { x: 10, y: 20 };
p.x = 30; // 合法:p 是 mut
println!("Point: x={}, y={}", p.x, p.y);
let c = Counter { count: Cell::new(0), name: "test".to_string() };
c.count.set(1); // 合法:count 是 Cell 类型,支持内部可变
// c.name = "new".to_string(); // 错误:name 是普通不可变字段
println!("Counter: count={}, name={}", c.count.get(), c.name); // 读取 count 和 name
}

实例化规则------所有权规则
字段若为非 Copy 类型(如 String、Vec),赋值时会转移所有权,原实例不可再用(除非 clone)
rust
struct Person { name: String }
fn main() {
let p1 = Person { name: "Bob".to_string() };
let p2 = p1; // 所有权转移给 p2,p1 不可再用
println!("{}", p1.name); // 错误:p1 已被移动
}

派生规则:#[derive] 的使用限制
-
只有 支持派生的 trait 才能用 #[derive](如 Debug、PartialEq),自定义 trait 需通过过程宏(Proc Macro)实现派生。
-
派生 trait 时,所有字段必须也实现该 trait(递归规则),如下面那个示例User里的Address字段也必须派生 Debug。
rust
#[derive(Debug)] // 正确:Point 字段 x/y 都是 i32,已实现 Debug
struct Point { x: i32, y: i32 }
#[derive(Debug)] // 正确:User 的字段 name(String)和 addr(Address)都实现 Debug
struct User { name: String, addr: Address }
#[derive(Debug)] // 必须为 Address 也派生 Debug
struct Address { street: String }
前面接触过派生宏,那这里又引入了一个新概念------过程宏(Proc Macro),过程宏是什么?它是一种特殊的 Rust 宏,接收 Rust 代码的语法树(TokenStream)作为输入,处理后输出新的 TokenStream(即生成的代码),运行在编译期。下面给出一个创建过程宏的示例,整体目录结构如下,
json
hello_macro_project/
├── Cargo.toml
├── hello_macro/ # 过程宏 crate
│ ├── Cargo.toml
│ └── src/lib.rs
├── hello_macro_trait/ # 普通库 crate(存放 trait)
│ ├── Cargo.toml
│ └── src/lib.rs
└── hello_macro_demo/ # 二进制测试 crate
├── Cargo.toml
└── src/main.rs
- 1.1 创建
print_fields_macro项目,并进入指定目录,
bash
# 创建hello_macro_project文件夹
mkdir hello_macro_project
# 创建工作区Cargo.toml
touch Cargo.toml
- 1.2 编辑工作区的Cargo.toml文件,
toml
[workspace]
members = [
"hello_macro",
"hello_macro_trait",
"hello_macro_demo",
]
resolver = "2"
- 1.3 创建普通库存放 trait
bash
cargo new hello_macro_trait --lib
cd hello_macro_trait
- 1.4 编辑hello_macro_trait/Cargo.toml
rust
[package]
name = "hello_macro_trait"
version = "0.1.0"
edition = "2021"
- 1.5 编辑hello_macro_trait/src/lib.rs
rust
// 定义 trait(普通库可正常导出)
pub trait HelloMacro {
fn hello_macro();
}
- 1.6 创建过程宏库
bash
cargo new hello_macro --lib
cd hello_macro
- 1.7 修改hello_macro/Cargo.toml,添加过程宏配置和依赖,
rust
[package]
name = "hello_macro"
version = "0.1.0"
edition = "2021"
[lib]
# 过程宏 crate 必须在 Cargo.toml 中声明 proc-macro = true,且只能依赖特定库(如 syn、quote、proc-macro2)
proc-macro = true
[dependencies]
syn = { version = "2.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
hello_macro_trait = { path = "../hello_macro_trait" } # 依赖 trait 库
- 1.8 然后创建 / 编辑src/lib.rs,写入过程宏代码。
rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
// 生成 trait 实现(引用外部库的 trait)
let expanded = quote! {
impl hello_macro_trait::HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
TokenStream::from(expanded)
}
- 1.9 返回工作区根目录,创建测试demo,
bash
cd ..
cargo new hello_macro_demo
cd hello_macro_demo
- 1.10 修改hello_macro_demo/Cargo.toml,添加对过程宏库的依赖,
rust
[package]
name = "hello_macro_demo"
version = "0.1.0"
edition = "2021"
[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_trait = { path = "../hello_macro_trait" } # 依赖 trait 库
- 1.11 然后编辑src/main.rs,写入测试代码
rust
use hello_macro::HelloMacro; // 引入派生宏
use hello_macro_trait::HelloMacro as _; // 引入 trait(或直接 use hello_macro_trait::HelloMacro;)
#[derive(HelloMacro)]
struct Person;
#[derive(HelloMacro)]
struct Book;
fn main() {
Person::hello_macro(); // 输出:Hello, Macro! My name is Person!
Book::hello_macro(); // 输出:Hello, Macro! My name is Book!
}
- 1.12 运行demo,
bash
cargo run -p hello_macro_demo

过程宏这块示例里有很多信息,需要慢慢消化,比如proc-macro和proc-macro2两个库,
| 特性 | proc-macro (Rust 内置) | proc-macro2 (第三方库) |
|---|---|---|
| 来源 | Rust 标准库,直接通过 proc_macro 模块提供 |
第三方库,需通过 proc-macro2 crate 引入 |
| 稳定性 | 依赖编译器内部实现,可能不稳定 | 提供稳定封装,兼容性更好 |
| TokenStream 类型 | 仅支持 proc_macro::TokenStream |
提供 proc_macro2::TokenStream,可转换为标准类型 |
| 调试支持 | 原生调试困难 | 提供更好的调试工具(如 to_string() 方法) |
| 跨版本兼容性 | 可能因编译器版本不同出现行为差异 | 屏蔽底层差异,保证跨版本行为一致 |
| 使用场景 | 直接用于声明式宏或过程宏的导出函数 | 推荐在宏实现逻辑中使用,再转换为标准类型输出 |
| 依赖关系 | 无需额外依赖 | 需添加 proc-macro2 = "1.x" 到 Cargo.toml |
可变结构体
若需修改结构体字段,实例必须声明为可变。如下所示,
rust
let mut rect1 = Rectangle {
width: 30,
height: 50,
};
rect1.width = 40;

元组结构体(Tuple Struct)
元组结构体省略字段名,仅保留字段类型。适用于轻量级封装,通过索引访问字段。
rust
// 定义元组结构体:Point(2D坐标,x=索引0,y=索引1)
struct Point(i32, f64);
// 定义元组结构体:Color(RGB颜色,红=0,绿=1,蓝=2)
struct Color(u8, u8, u8);
fn main() {
// 实例化:按类型顺序传值(必须匹配定义的类型)
let p = Point(10, 3.14);
let red = Color(255, 0, 0);
// 通过索引访问字段(0 对应第一个字段,1 对应第二个,以此类推)
println!("Point x: {}, y: {}", p.0, p.1); // 输出:Point x: 10, y: 3.14
println!("Red color: R={}, G={}, B={}", red.0, red.1, red.2); // 输出:Red color: R=255, G=0, B=0
}

单元结构体(Unit Struct)
如果元组结构体没有字段(空括号),称为 "单元结构体",类似 ()(单元类型)。单元结构体(无字段)本身不存储数据,主要用于:
- 标记类型(比如区分不同的 "状态")
- 实现 trait 的载体(比如某些无状态的 trait 实现)
rust
// 为单元结构体实现 Debug(用于打印)
#[derive(Debug)]
struct VisibleMarker;
fn main() {
let m = VisibleMarker;
println!("{:?}", m); // 输出:VisibleMarker
}

- 为什么需要 Debug?
{:?}是Rust 提供的 "调试格式化符",专门用于开发时打印变量信息,依赖std::fmt::Debugtrait。所有基本类型(i32、String等)默认已实现 Debug,但自定义结构体(包括元组结构体、单元结构体)需要手动推导或实现。- 扩展:常用可派生的 trait
除了 Debug,Rust 还支持通过#[derive]自动派生其他常用 trait,比如:
PartialEq/Eq:用于比较相等性(==/!=)Clone/Copy:用于复制值Default:用于生成默认实例
比如下面同时派生出多个 trait
rust
// 同时派生多个 trait
#[derive(Debug, PartialEq, Clone)]
struct Point(i32, f64);
fn main() {
let p1 = Point(10, 3.14);
let p2 = p1.clone(); // 因为派生了 Clone
println!("p1: {:?}, p2: {:?}", p1, p2); // 因为派生了 Debug
println!("p1 == p2: {}", p1 == p2); // 因为派生了 PartialEq(输出 true)
}

嵌套元组结构体
元组结构体的字段可以是任意类型,包括其他元组结构体:
rust
// 定义元组结构体:Point(2D坐标,x=索引0,y=索引1)
struct Point(i32, i32);
// 嵌套元组结构体:Rect 由两个 Point 组成(左上角和右下角)
struct Rect(Point, Point);
fn main() {
let top_left = Point(0, 0);
let bottom_right = Point(100, 200);
let rect = Rect(top_left, bottom_right);
// 嵌套访问:先访问 Rect 的字段,再访问 Point 的字段
println!("左上角 x: {}", rect.0.0); // rect.0 是 top_left,再 .0 是 x
println!("右下角 y: {}", rect.1.1); // rect.1 是 bottom_right,再 .1 是 y
}

以下是Rust中各类结构体的主要区别:
| 类型 | 定义语法 | 可变性 | 字段命名 | 内存布局 | 典型用途 |
|---|---|---|---|---|---|
| 普通结构体 | struct Name { field: Type } |
默认不可变,需加mut声明可变 |
具名字段 | 字段顺序不固定 | 复杂数据建模,需清晰字段名 |
| 可变结构体 | let mut s = Name { ... } |
实例声明为mut后可修改字段 |
具名字段 | 同普通结构体 | 需要修改内部状态的场景 |
| 元组结构体 | struct Name(Type1, Type2) |
类似普通结构体 | 匿名字段(通过索引访问) | 顺序内存布局 | 轻量级包装,语义化元组 |
| 单元结构体 | struct Name; |
无字段故不可变 | 无字段 | 零大小(ZST) | 标记 trait 实现或空行为 |
| 嵌套元组结构体 | struct Nested((Type1, Type2)) |
取决于外层结构体 | 外层具名,内层匿名 | 嵌套顺序布局 | 组合已有元组结构 |
| 普通元组 | (Type1, Type2) |
元素独立可变性 | 匿名字段(通过索引访问) | 顺序内存布局 | 临时数据组合,无需类型命名 |
泛型结构体
结构体可使用泛型参数,实现类型复用:
rust
struct GenericStruct<T, U> {
data1: T,
data2: U,
}
fn main() {
let s1 = GenericStruct { data1: 10, data2: "hello" };
let s2 = GenericStruct { data1: 3.14, data2: true };
println!("s1.data1: {}, s1.data2: {}", s1.data1, s1.data2);
println!("s2.data1: {}, s2.data2: {}", s2.data1, s2.data2);
}

结构体更新语法(Struct Update Syntax)
..的作用是,基于已有结构体实例,快速创建新实例------ 新实例可复用原实例的大部分字段值,仅修改需要变更的字段,避免重复编写所有字段。
该语法同时支持 普通结构体(命名字段) 和 元组结构体(无名字段),但用法和限制略有不同,
普通结构体(命名字段)
下面是一个普通结构体的示例,
rust
// 定义普通结构体(命名字段)
struct Person {
name: String,
age: u8,
city: String,
is_student: bool,
}
fn main() {
// 1. 创建原始实例
let alice = Person {
name: "Alice".to_string(),
age: 22,
city: "Beijing".to_string(),
is_student: true,
};
// 2. 用 .. 复用 alice 的大部分字段,仅修改 age 和 city
let bob = Person {
name: "Bob".to_string(), // 新值:覆盖原字段
age: 25, // 新值:覆盖原字段
..alice // 复用剩余所有字段(city、is_student)
};
println!("Bob: name={}, age={}, city={}, student={}",
bob.name, bob.age, bob.city, bob.is_student);
// 输出:Bob: name=Bob, age=25, city=Beijing, student=true
}
其结果是通过..直接复用alice对象的大部分字段。

..使用规则必须遵循下面几点:
规则1:.. 必须放在结构体初始化的最后
因为.. 表示 "剩余所有字段",顺序不能颠倒(编译会报错)。
rust
let bob = Person {
..alice,
name: "Bob".to_string(), // 新值:覆盖原字段
age: 25 // 新值:覆盖原字段
};
比如按照上面这个顺序,编译会报错,

规则2:复用的字段会转移所有权(非 Copy 类型)
rust
// 定义普通结构体(命名字段)
struct Person {
name: String,
age: u8,
city: String,
is_student: bool,
}
fn main() {
// 1. 创建原始实例
let alice = Person {
name: "Alice".to_string(),
age: 22,
city: "Beijing".to_string(),
is_student: true,
};
// 2. 用 .. 复用 alice 的大部分字段,仅修改 age 和 city
let bob = Person {
name: "Bob".to_string(), // 新值:覆盖原字段
age: 25, // 新值:覆盖原字段
..alice // 复用剩余所有字段(city、is_student)
};
println!("Bob: name={}, age={}, city={}, student={}",
bob.name, bob.age, bob.city, bob.is_student);
// 输出:Bob: name=Bob, age=25, city=Beijing, student=true
// 错误:alice 的 city 字段已转移给 bob,alice 不再完整
println!("Alice's city: {}", alice.city);
}
还是上面的例子,但是后续再去使用alice的city属性就会报错,

规则3:Copy 类型字段会复制,不转移所有权
如果字段是 u8、bool 等 Copy 类型,... 会直接复制值,原实例仍可正常使用。如下面的例子所示,
rust
// 示例:字段均为 Copy 类型
#[derive(Debug, Copy, Clone)]
struct Point { x: i32, y: i32 }
fn main() {
let p1 = Point { x: 10, y: 20 };
let p2 = Point { x: 30, ..p1 }; // y 字段复制,p1 仍可用
println!("p1: {:?}, p2: {:?}", p1, p2); // 正常输出
}
Rust中常见的derive宏可应用的trait分类如下表所示,
| 类别 | Trait名称 | 功能描述 |
|---|---|---|
| 基本类型 | Debug |
提供{:?}格式化输出,用于调试打印 |
Clone |
允许显式创建值的深拷贝 | |
Copy |
标记类型为按位复制语义(需先实现Clone) |
|
Default |
提供类型的默认值 | |
| 比较操作 | PartialEq |
支持部分相等比较(如浮点数NaN) |
Eq |
标记完全等价关系(需先实现PartialEq) |
|
PartialOrd |
支持部分排序(如浮点数) | |
Ord |
支持全序排序(需先实现PartialOrd + Eq) |
|
| 哈希与序列化 | Hash |
支持哈希值计算 |
Serialize |
支持序列化(需配合serde库) |
|
Deserialize |
支持反序列化(需配合serde库) |
|
| 其他 | From |
支持从其他类型转换(需手动实现反向Into) |
Into |
支持转换为目标类型(通常由From自动实现) |
元组结构体(无名字段)
元组结构体无字段名,.. 语法的作用是 复用原实例的剩余字段,但需注意:必须显式指定 "要修改的字段",且 .. 只能放在最后(表示 "剩余所有位置的字段都复用原实例")。
rust
struct Point(i32, i32, i32);
fn main() {
let point = Point(10, 20, 30);
// 使用 `..` 忽略剩余字段
let Point(x, ..) = point;
println!("x: {}", x); // 输出: x: 10
}
下面的提示warning是Point结构体的第二个和第三个参数没有用到过,

嵌套结构体
在嵌套结构体中,.. 语法用于逐层访问内部结构体的成员。通过连续使用点操作符,可以深入访问嵌套的每一级成员。
rust
struct Point{
x : u32,
y : u32,
}
struct Size {
width: u32,
height: u32,
}
struct Rectangle {
origin: Point, // 嵌套 Point 结构体
size: Size, // 嵌套 Size 结构体
}
fn main() {
let base_rect = Rectangle {
origin: Point { x: 0, y: 0 },
size: Size { width: 100, height: 200 },
};
// 基于 base_rect 创建新矩形:仅修改 origin.x 和 size.height
let new_rect = Rectangle {
origin: Point { x: 50, ..base_rect.origin }, // 复用 base_rect.origin 的 y
size: Size { height: 250, ..base_rect.size }, // 复用 base_rect.size 的 width
..base_rect // 若其他字段未修改,可直接用 ..base_rect 兜底(此处可省略,仅演示)
};
println!(
"new_rect: origin=({},{}), size=({}, {})",
new_rect.origin.x, new_rect.origin.y,
new_rect.size.width, new_rect.size.height
);
// 输出:new_rect: origin=(50,0), size=(100, 250)
}

与 Default trait 结合(默认值复用)
.. 语法能复用已有实例的字段值,结合 Default::default() 可快速基于默认值创建自定义实例。
rust
#[derive(Debug, Default)]
struct Config {
host: String, // String 默认值:空字符串
port: u16, // u16 默认值:0
timeout: u32, // u32 默认值:0
enable_ssl: bool, // bool 默认值:false
}
fn main() {
// 基于默认值,仅修改部分字段
let custom_config = Config {
host: "localhost".to_string(),
port: 8080,
enable_ssl: true,
..Config::default() // 其余字段用默认值
};
println!("{:#?}", custom_config);
// 输出:
// Config {
// host: "localhost",
// port: 8080,
// timeout: 0,
// enable_ssl: true,
// }
}

总结
Rust 结构体是数据与行为的结合体,既提供灵活的复合类型定义,又通过所有权、可变性规则保证内存安全。无论是简单数据封装还是复杂业务模型,结构体都是 Rust 编程中不可或缺的核心工具,掌握其设计思想和使用规则是构建健壮 Rust 程序的关键。