第十四篇:Rust Derive 宏详解

38 阅读8分钟

Rust Derive 宏详解

目录

什么是 Derive 宏

Derive 宏是 Rust 的一种过程宏(Procedural Macro),用于自动为类型生成 trait 实现代码。它可以大幅减少样板代码,提高开发效率。

基本语法

#[derive(Trait1, Trait2, Trait3)]
pub struct MyStruct {
    field1: Type1,
    field2: Type2,
}

编译器会自动为 MyStruct 生成 Trait1Trait2Trait3 的实现。

标准库常用 Derive

1. Debug

作用: 允许使用 {:?} 和 {:#?} 格式化打印,便于调试。

#[derive(Debug)]
struct User {
    id: i64,
    username: String,
    email: String,
}

fn main() {
    let user = User {
        id: 1,
        username: "alice".to_string(),
        email: "alice@example.com".to_string(),
    };
    
    // 单行格式
    println!("{:?}", user);
    // 输出: User { id: 1, username: "alice", email: "alice@example.com" }
    
    // 多行美化格式
    println!("{:#?}", user);
    // 输出:
    // User {
    //     id: 1,
    //     username: "alice",
    //     email: "alice@example.com",
    // }
}

使用场景:

  • 调试和日志记录
  • 开发过程中快速查看结构体内容
  • 错误信息输出

2. Clone

作用: 允许显式复制类型的值。

#[derive(Debug, Clone)]
struct Config {
    host: String,
    port: u16,
}

fn main() {
    let config1 = Config {
        host: "localhost".to_string(),
        port: 3000,
    };
    
    // 使用 clone() 方法复制
    let config2 = config1.clone();
    
    println!("{:?}", config1); // config1 仍然可用
    println!("{:?}", config2); // config2 是独立的副本
}

深拷贝 vs 浅拷贝:

  • Clone 通常是深拷贝(包括堆数据)
  • 对于基本类型(i32, f64 等),Clone 和 Copy 效果相同
  • 对于 String、Vec 等,Clone 会分配新内存

使用场景:

  • 需要保留原始值的同时创建副本
  • 多线程之间共享数据时复制
  • Axum 等框架要求状态类型实现 Clone

3. Copy

作用: 允许类型通过简单内存复制来复制,不需要显式调用方法。

#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 10, y: 20 };
    let p2 = p1; // 自动复制,不是移动
    
    println!("{:?}", p1); // p1 仍然可用
    println!("{:?}", p2);
}

限制条件:

  • 只能用于固定大小的类型
  • 所有字段也必须实现 Copy
  • 不能包含 String、Vec 等堆分配类型
  • 实现 Copy 必须同时实现 Clone

使用场景:

  • 基本数值类型的包装
  • 坐标、颜色等简单结构
  • 状态标志和枚举

4. PartialEq 和 Eq

作用: 允许使用 == 和 != 比较值。

#[derive(Debug, PartialEq)]
struct User {
    id: i64,
    username: String,
}

fn main() {
    let user1 = User { id: 1, username: "alice".to_string() };
    let user2 = User { id: 1, username: "alice".to_string() };
    let user3 = User { id: 2, username: "bob".to_string() };
    
    assert_eq!(user1, user2); // 通过
    assert_ne!(user1, user3); // 通过
}

PartialEq vs Eq:

  • PartialEq: 允许部分相等(如浮点数,NaN != NaN)
  • Eq: 要求完全相等(自反性、对称性、传递性)
  • Eq 是 PartialEq 的子 trait,没有额外方法
#[derive(Debug, PartialEq, Eq)]
struct User {
    id: i64,
}
// 不能对包含 f64 的类型 derive Eq

5. PartialOrd 和 Ord

作用: 允许比较大小(<><=>=)和排序。

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Priority {
    level: u8,
}

fn main() {
    let low = Priority { level: 1 };
    let high = Priority { level: 10 };
    
    assert!(low < high);
    
    let mut priorities = vec![high, low];
    priorities.sort(); // 需要 Ord
}

依赖关系:

  • PartialOrd 需要 PartialEq
  • Ord 需要 Eq 和 PartialOrd

6. Hash

作用: 允许类型作为 HashMap、HashSet 的键。

use std::collections::HashMap;

#[derive(Debug, PartialEq, Eq, Hash)]
struct UserId(i64);

fn main() {
    let mut map = HashMap::new();
    map.insert(UserId(1), &#34;Alice&#34;);
    map.insert(UserId(2), &#34;Bob&#34;);
    
    println!(&#34;{:?}&#34;, map.get(&UserId(1))); // Some(&#34;Alice&#34;)
}

要求: 必须同时实现 Eq

7. Default

作用: 为类型提供默认值。

#[derive(Debug, Default)]
struct Config {
    host: String,        // 默认 &#34;&#34;
    port: u16,           // 默认 0
    enabled: bool,       // 默认 false
}

fn main() {
    let config = Config::default();
    println!(&#34;{:?}&#34;, config);
    // Config { host: &#34;&#34;, port: 0, enabled: false }
    
    // 部分字段使用默认值
    let config2 = Config {
        host: &#34;localhost&#34;.to_string(),
        ..Default::default()
    };
}

第三方库 Derive

1. Serialize 和 Deserialize (serde)

作用: 序列化和反序列化数据(JSON、TOML、YAML 等)。

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub username: String,
    pub email: String,
}

fn main() {
    // 序列化为 JSON
    let user = User {
        id: 1,
        username: &#34;alice&#34;.to_string(),
        email: &#34;alice@example.com&#34;.to_string(),
    };
    
    let json = serde_json::to_string(&user).unwrap();
    println!(&#34;{}&#34;, json);
    // {&#34;id&#34;:1,&#34;username&#34;:&#34;alice&#34;,&#34;email&#34;:&#34;alice@example.com&#34;}
    
    // 反序列化
    let user2: User = serde_json::from_str(&json).unwrap();
    println!(&#34;{:?}&#34;, user2);
}

常用属性:

#[derive(Serialize, Deserialize)]
pub struct User {
    #[serde(rename = &#34;userId&#34;)]  // JSON 中使用 userId
    pub id: i64,
    
    #[serde(skip)]  // 跳过该字段
    pub password_hash: String,
    
    #[serde(default)]  // 反序列化时缺失则使用默认值
    pub role: String,
    
    #[serde(skip_serializing_if = &#34;Option::is_none&#34;)]  // None 时不序列化
    pub nickname: Option,
}

2. FromRow (sqlx)

作用: 将数据库查询结果自动转换为 Rust 结构体。

use sqlx::FromRow;

#[derive(Debug, FromRow)]
pub struct User {
    pub id: i64,
    pub username: String,
    pub email: String,
}

// 使用示例
async fn get_user(pool: &MySqlPool, id: i64) -> Result {
    let user = sqlx::query_as::(
        &#34;SELECT id, username, email FROM users WHERE id = ?&#34;
    )
    .bind(id)
    .fetch_one(pool)
    .await?;
    
    Ok(user)
}

要求:

  • 字段名必须与数据库列名匹配(或使用 #[sqlx(rename = &#34;...&#34;)]
  • 字段类型必须与数据库列类型兼容

3. Error (thiserror)

作用: 简化自定义错误类型的创建。

use thiserror::Error;

#[derive(Error, Debug)]
pub enum JwtError {
    #[error(&#34;JWT 生成失败&#34;)]
    EncodingError(#[from] jsonwebtoken::errors::Error),
    
    #[error(&#34;JWT 验证失败&#34;)]
    ValidationError,
    
    #[error(&#34;JWT 已过期&#34;)]
    Expired,
    
    #[error(&#34;无效的 JWT: {0}&#34;)]
    Invalid(String),
}

fn main() {
    let err = JwtError::Expired;
    println!(&#34;{}&#34;, err); // JWT 已过期
}

属性说明:

  • #[error(&#34;...&#34;)]: 定义错误显示消息
  • #[from]: 自动实现 From trait,支持 ? 操作符转换

项目中的实际应用

示例 1: 数据库实体

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;

#[derive(Debug, Serialize, Deserialize, FromRow, Clone)]
pub struct User {
    pub id: i64,
    pub username: String,
    pub email: String,
    pub password_hash: String,
    pub created_at: DateTime,
    pub updated_at: DateTime,
}

为什么这样组合?

  • Debug: 调试时打印结构体
  • Serialize: 转换为 JSON 返回给客户端(或存入 Redis)
  • Deserialize: 从 JSON 解析(从 Redis 读取)
  • FromRow: 从数据库查询结果转换
  • Clone: 允许复制,用于缓存和共享

示例 2: API 请求 DTO

#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
    pub username: String,
    pub email: String,
    pub password: String,
}

为什么只有 Deserialize?

  • 这是输入类型,只需要从 JSON 反序列化
  • 不需要序列化(不会作为响应返回)
  • Debug 用于日志和错误信息

示例 3: API 响应 DTO

#[derive(Debug, Serialize)]
pub struct UserResponse {
    pub id: i64,
    pub username: String,
    pub email: String,
    pub created_at: DateTime,
    pub updated_at: DateTime,
}

为什么只有 Serialize?

  • 这是输出类型,只需要序列化为 JSON
  • 不需要反序列化(不从客户端接收)
  • 注意:去除了 password_hash 字段,保护隐私

示例 4: 应用状态

use sqlx::MySqlPool;
use crate::utils::cache::RedisCache;

#[derive(Clone)]
pub struct AppState {
    pub db: MySqlPool,
    pub cache: RedisCache,
}

为什么只需要 Clone?

  • Axum 框架要求状态必须实现 Clone
  • MySqlPool 和 RedisCache 内部使用 Arc,clone 成本低
  • 不需要 Debug、Serialize 等

示例 5: 自定义错误类型

use thiserror::Error;

#[derive(Error, Debug)]
pub enum JwtError {
    #[error(&#34;JWT 生成失败&#34;)]
    EncodingError(#[from] jsonwebtoken::errors::Error),
    
    #[error(&#34;JWT 验证失败&#34;)]
    ValidationError,
    
    #[error(&#34;JWT 已过期&#34;)]
    Expired,
}

组合说明:

  • Error: thiserror 自动实现 std::error::Error
  • Debug: 必需,用于错误传播
  • #[from]: 支持从其他错误类型自动转换

最佳实践

1. 选择合适的 Derive

根据类型用途选择必要的 Derive:

类型用途推荐 Derive
数据库实体Debug, Serialize, Deserialize, FromRow, Clone
API 请求Debug, Deserialize
API 响应Debug, Serialize
内部状态Clone
配置Debug, Clone, Deserialize, Default
错误类型Error, Debug
简单值对象Debug, Clone, Copy, PartialEq, Eq

2. 避免过度 Derive

// ❌ 不好:不需要的 trait
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct UserRequest {
    pub username: String,  // ❌ String 不能 Copy
}

// ✅ 好:只 derive 需要的
#[derive(Debug, Deserialize)]
pub struct UserRequest {
    pub username: String,
}

3. 注意依赖顺序

某些 Derive 有依赖关系:

// ✅ 正确顺序
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Priority(u8);

// ❌ 错误:Ord 需要 Eq
#[derive(Debug, Clone, PartialEq, Ord)]  // 编译失败
pub struct Priority(u8);

4. 使用属性控制行为

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct User {
    pub id: i64,
    
    #[serde(skip_serializing)]  // 序列化时跳过
    pub password_hash: String,
    
    #[sqlx(rename = &#34;user_email&#34;)]  // 映射到不同的列名
    pub email: String,
    
    #[serde(default)]  // 使用默认值
    pub role: String,
}

5. 手动实现复杂逻辑

当自动实现不满足需求时,手动实现:

#[derive(Debug, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub username: String,
}

// 手动实现 PartialEq,只比较 id
impl PartialEq for User {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for User {}

常见组合模式

模式 1: 完整数据模型

#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
pub struct Book {
    pub id: i64,
    pub title: String,
    pub author: String,
    pub isbn: String,
}

模式 2: 轻量值对象

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(pub i64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Priority(pub u8);

模式 3: 配置对象

#[derive(Debug, Clone, Deserialize, Default)]
pub struct Config {
    #[serde(default = &#34;default_host&#34;)]
    pub host: String,
    
    #[serde(default = &#34;default_port&#34;)]
    pub port: u16,
}

fn default_host() -> String {
    &#34;127.0.0.1&#34;.to_string()
}

fn default_port() -> u16 {
    3000
}

模式 4: 枚举状态

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = &#34;lowercase&#34;)]
pub enum Status {
    Pending,
    Active,
    Completed,
    Failed,
}

模式 5: 结果类型

#[derive(Debug, Serialize)]
#[serde(tag = &#34;status&#34;, content = &#34;data&#34;)]
pub enum ApiResponse {
    Success(T),
    Error { message: String, code: u16 },
}

性能考虑

Clone 的开销

// 开销小:只复制引用计数
#[derive(Clone)]
pub struct AppState {
    pub db: Arc,
    pub cache: Arc,
}

// 开销大:深拷贝所有数据
#[derive(Clone)]
pub struct LargeData {
    pub content: Vec,  // 可能很大
    pub metadata: HashMap,
}

Serialize/Deserialize 优化

// 使用 skip_serializing_if 减少 JSON 大小
#[derive(Serialize)]
pub struct Response {
    pub data: Vec,
    
    #[serde(skip_serializing_if = &#34;Option::is_none&#34;)]
    pub next_page: Option,
    
    #[serde(skip_serializing_if = &#34;Vec::is_empty&#34;)]
    pub warnings: Vec,
}

总结

Derive 宏的使用原则:

  1. 按需选择: 只 derive 实际需要的 trait
  2. 理解依赖: 注意 trait 之间的依赖关系
  3. 控制行为: 使用属性精确控制生成代码
  4. 手动覆盖: 复杂逻辑手动实现
  5. 性能意识: 理解各个 trait 的性能影响