Rust 结构体与方法

0 阅读9分钟

Rust 结构体与方法

主题:Day 7 - 结构体与方法


第一层:原理层(Why & How)

为什么需要结构体?

在编程中,我们经常需要将相关的数据组合在一起。例如,描述一个用户需要:姓名、年龄、邮箱等多个属性。

传统方式(元组)的问题

// 元组:可读性差,无法自描述
let user = ("Alice", 30, "alice@example.com");
// 访问方式:user.0, user.1, user.2 - 不直观

结构体解决方案

// 结构体:字段有名字,语义清晰
struct User {
    name: String,
    age: u32,
    email: String,
}

let user = User {
    name: String::from("Alice"),
    age: 30,
    email: String::from("alice@example.com"),
};
// 访问方式:user.name, user.age, user.email - 一目了然

结构体的内存布局

┌─────────────────────────────────────────┐
│              User 结构体                 │
├─────────────────────────────────────────┤
│  name: String                           │
│  ├── ptr: *u8      ──→ "Alice" (堆)     │
│  ├── len: usize    = 5                  │
│  └── capacity: usize = 5                │
├─────────────────────────────────────────┤
│  age: u32         = 30                  │
├─────────────────────────────────────────┤
│  email: String                          │
│  ├── ptr: *u8      ──→ "alice@..." (堆) │
│  ├── len: usize    = 17                 │
│  └── capacity: usize = 17               │
└─────────────────────────────────────────┘

内存对齐:结构体字段按最大对齐值对齐
User 总大小 = String(24) + u32(4) + String(24) + padding(4) = 56 bytes

内存布局原则

  1. 顺序存储:字段按定义顺序在内存中排列
  2. 对齐填充:编译器自动插入 padding 以满足对齐要求
  3. 所有权独立:每个字段拥有独立的所有权

方法的哲学

Rust 的方法设计体现了面向数据编程的思想:

数据(结构体) + 行为(方法) = 完整的抽象

┌─────────────────────────────────────────┐
│           Rectangle 结构体              │
├─────────────────────────────────────────┤
│  数据                                   │
│  ├── width: u32                         │
│  └── height: u32                        │
├─────────────────────────────────────────┤
│  行为(方法)                            │
│  ├── area(&self) -> u32                 │
│  ├── can_hold(&self, other) -> bool     │
│  └── square(size) -> Rectangle          │
└─────────────────────────────────────────┘

与其他语言对比

特性RustPythonJavaC++
方法定义impl类内部类内部类内部
self 参数显式隐式 self隐式 this隐式 this
所有权显式控制引用计数/GCGC手动/智能指针
静态方法Self::method()@staticmethodstaticstatic

self 参数的本质

impl Rectangle {
    // &self 实际上是 &Rectangle 的语法糖
    fn area(&self) -> u32 {
        self.width * self.height
    }
    // 等价于:
    // fn area(self: &Rectangle) -> u32
}

三种 self 形式

形式含义使用场景
self获取所有权方法消耗实例
&self不可变借用只读访问
&mut self可变借用修改实例

第二层:实战层(What & Do)

1. 定义结构体

具名结构体(Named Struct)
// 定义
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

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

// 访问字段
println!("用户名: {}", user1.username);
println!("邮箱: {}", user1.email);
元组结构体(Tuple Struct)
// 没有字段名,只有类型
struct Point(i32, i32, i32);
struct Color(i32, i32, i32);

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

// 访问:按索引
println!("x: {}", origin.0);
println!("y: {}", origin.1);

用途

  • 给元组起个有意义的名字
  • 创建不同的类型(Point 和 Color 是不同的类型)
单元结构体(Unit Struct)
// 没有任何字段
struct AlwaysEqual;

let subject = AlwaysEqual;

用途

  • 标记类型
  • 实现 trait 时不需要存储数据

2. 结构体更新语法

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

// 基于 user1 创建 user2,只修改 email
let user2 = User {
    email: String::from("user2@example.com"),
    ..user1  // 其余字段从 user1 复制
};

// 注意:username 发生了移动,user1 不能再使用
// println!("{}", user1.username); // 错误!

3. 定义方法

struct Rectangle {
    width: u32,
    height: u32,
}

// impl 块:实现结构体的方法
impl Rectangle {
    // 方法:第一个参数是 self
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 方法:可以访问其他方法
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }

    // 可变方法:修改 self
    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }
}

// 使用
fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("面积: {}", rect1.area());  // 自动解引用为 &Rectangle

    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    println!("rect1 能容纳 rect2? {}", rect1.can_hold(&rect2));
}

4. 关联函数(静态方法)

impl Rectangle {
    // 关联函数:没有 self 参数
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }

    // 构造函数也是关联函数
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

// 使用 :: 调用
let sq = Rectangle::square(3);
let rect = Rectangle::new(10, 20);

调用语法对比

类型调用方式示例
方法对象.方法()rect.area()
关联函数类型::函数()Rectangle::square(3)

5. 多个 impl 块

struct Rectangle {
    width: u32,
    height: u32,
}

// impl 块 1
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

// impl 块 2(可以分开,通常用于 trait 实现)
impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

用途

  • 组织代码
  • 为泛型实现不同约束的方法

6. 结构体与所有权

struct User {
    name: String,      // 拥有所有权
    email: String,     // 拥有所有权
    age: u32,          // Copy 类型,直接复制
}

fn main() {
    let user = User {
        name: String::from("Alice"),
        email: String::from("alice@example.com"),
        age: 30,
    };

    // 移动整个结构体
    let user2 = user;
    // println!("{}", user.name); // 错误!user 已被移动

    // 借用
    let user3 = User {
        name: String::from("Bob"),
        email: String::from("bob@example.com"),
        age: 25,
    };
    print_user(&user3);  // 借用
    println!("{}", user3.name);  // 仍然可用
}

fn print_user(user: &User) {
    println!("{} - {}", user.name, user.email);
}

7. 派生 trait

// 自动派生 Debug trait,支持 {:?} 打印
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

// 自动派生多个 trait
#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("{:?}", rect);  // Rectangle { width: 30, height: 50 }
    println!("{:#?}", rect); // 美化打印
}

常用可派生 trait

Trait功能要求
Debug调试输出 {:?}所有字段实现 Debug
Clone显式克隆 .clone()所有字段实现 Clone
Copy隐式复制所有字段是 Copy 类型
PartialEq相等比较 ==所有字段实现 PartialEq
Eq完全相等满足 PartialEq + 自反性
PartialOrd部分排序所有字段实现 PartialOrd
Ord完全排序满足 PartialOrd + 全序
Hash哈希计算所有字段实现 Hash
Default默认值 Default::default()所有字段实现 Default

第三层:最佳实践(Production Ready)

1. 构造函数模式

struct Config {
    host: String,
    port: u16,
    timeout: Duration,
}

impl Config {
    // 默认构造函数
    fn new(host: &str, port: u16) -> Self {
        Self {
            host: host.to_string(),
            port,
            timeout: Duration::from_secs(30),
        }
    }

    // Builder 模式
    fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }
}

// 使用
let config = Config::new("localhost", 8080)
    .with_timeout(Duration::from_secs(60));

2. 使用 &str 替代 String

// ❌ 不推荐:强制拥有所有权
struct Config {
    name: String,
}

// ✅ 推荐:使用引用,更灵活
struct Config<'a> {
    name: &'a str,
}

// 或者使用 Cow(Clone on Write)
use std::borrow::Cow;

struct Config<'a> {
    name: Cow<'a, str>,
}

3. 使用 Option 处理可选字段

struct User {
    name: String,
    email: String,
    phone: Option<String>,      // 可选
    address: Option<String>,    // 可选
    age: Option<u32>,           // 可选
}

impl User {
    fn new(name: &str, email: &str) -> Self {
        Self {
            name: name.to_string(),
            email: email.to_string(),
            phone: None,
            address: None,
            age: None,
        }
    }
}

4. 使用类型别名增强语义

// ❌ 不推荐:魔法数字
type Point = (i32, i32, i32);

// ✅ 推荐:命名结构体
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

// 或者使用 newtype 模式
type UserId = u64;
type OrderId = u64;

// 更好的方式:使用结构体包装
struct UserId(u64);
struct OrderId(u64);

// 这样 UserId 和 OrderId 是不同的类型,不能混淆

5. 实现 Display trait

use std::fmt;

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

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

// 使用
let p = Point { x: 1, y: 2 };
println!("{}", p);  // 输出: (1, 2)

6. 避免过度使用结构体更新语法

// ❌ 不推荐:可能产生意外的所有权转移
let user2 = User {
    email: String::from("new@example.com"),
    ..user1
};
// user1 的某些字段可能已被移动

// ✅ 推荐:显式复制需要的字段
let user2 = User {
    name: user1.name.clone(),
    email: String::from("new@example.com"),
    age: user1.age,
    active: user1.active,
};
// user1 仍然有效

7. 使用 derive 宏

// ✅ 推荐:自动派生常用 trait
#[derive(Debug, Clone, PartialEq, Default)]
struct Rectangle {
    width: u32,
    height: u32,
}

// 使用 Default
let rect: Rectangle = Default::default();  // width=0, height=0

// 使用 PartialEq
assert_eq!(rect, Rectangle { width: 0, height: 0 });

第四层:问题诊断(Troubleshooting)

问题 1:结构体字段所有权转移

症状:使用结构体更新语法后,原结构体无法访问

let user1 = User {
    name: String::from("Alice"),
    email: String::from("alice@example.com"),
    age: 30,
};

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

println!("{}", user1.name);  // 错误!name 已被移动到 user2

诊断

  • String 类型没有实现 Copy trait
  • 结构体更新语法会移动未显式指定的字段

解决方案

// 方案 1:显式 clone
let user2 = User {
    name: user1.name.clone(),
    email: String::from("bob@example.com"),
    age: user1.age,
};

// 方案 2:使用引用
struct User<'a> {
    name: &'a str,
    email: &'a str,
    age: u32,
}

问题 2:方法调用时所有权错误

症状:方法调用后无法继续使用实例

impl Rectangle {
    // ❌ 错误:获取了所有权
    fn consume(self) {
        println!("消费了 {:?}", self);
    }
}

let rect = Rectangle { width: 10, height: 20 };
rect.consume();
rect.area();  // 错误!rect 已被消费

解决方案

impl Rectangle {
    // ✅ 正确:使用 &self
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

let rect = Rectangle { width: 10, height: 20 };
rect.area();
rect.area();  // 可以多次调用

问题 3:可变借用与不可变借用冲突

症状:编译错误 "cannot borrow as mutable because it is also borrowed as immutable"

let mut rect = Rectangle { width: 10, height: 20 };

let area = rect.area();  // 不可变借用 &self
rect.scale(2);           // 可变借用 &mut self

println!("{}", area);    // 错误!area 仍然借用 rect

解决方案

let mut rect = Rectangle { width: 10, height: 20 };

let area = rect.area();
println!("{}", area);    // 先使用完不可变借用

rect.scale(2);           // 再进行可变借用

问题 4:结构体递归定义

症状:编译错误 "has infinite size"

// ❌ 错误:递归定义
struct Node {
    value: i32,
    next: Node,  // 错误!Node 包含自己
}

解决方案

// ✅ 正确:使用 Box 间接引用
struct Node {
    value: i32,
    next: Option<Box<Node>>,  // Box 在堆上分配
}

// 或者使用引用(需要生命周期)
struct Node<'a> {
    value: i32,
    next: Option<&'a Node<'a>>,
}

问题 5:derive 宏失败

症状:编译错误 "the trait bound is not satisfied"

#[derive(Clone)]
struct User {
    name: String,
    data: Vec<u8>,
    // 某个字段没有实现 Clone
}

诊断

  • 所有字段必须实现对应的 trait
  • 检查每个字段的类型

解决方案

// 手动实现 Clone
impl Clone for User {
    fn clone(&self) -> Self {
        Self {
            name: self.name.clone(),
            data: self.data.clone(),
        }
    }
}

// 或者跳过某些字段
#[derive(Clone)]
struct User {
    name: String,
    #[allow(dead_code)]
    data: Vec<u8>,  // 手动处理
}

第五层:权威引用(References)

官方文档

标准库文档

社区资源

推荐书籍

  • 《The Rust Programming Language》(官方教程)
  • 《Programming Rust》(O'Reilly)
  • 《Rust in Action》

总结

概念说明示例
具名结构体带字段名的自定义类型struct User { name: String }
元组结构体无字段名的结构体struct Point(i32, i32)
单元结构体无字段的结构体struct Empty;
方法第一个参数是 selffn area(&self) -> u32
关联函数无 self 参数fn square(size: u32) -> Self
derive自动实现 trait#[derive(Debug, Clone)]