Rust Derive 宏详解
目录
什么是 Derive 宏
Derive 宏是 Rust 的一种过程宏(Procedural Macro),用于自动为类型生成 trait 实现代码。它可以大幅减少样板代码,提高开发效率。
基本语法
#[derive(Trait1, Trait2, Trait3)]
pub struct MyStruct {
field1: Type1,
field2: Type2,
}
编译器会自动为 MyStruct 生成 Trait1、Trait2、Trait3 的实现。
标准库常用 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需要PartialEqOrd需要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), "Alice");
map.insert(UserId(2), "Bob");
println!("{:?}", map.get(&UserId(1))); // Some("Alice")
}
要求: 必须同时实现 Eq
7. Default
作用: 为类型提供默认值。
#[derive(Debug, Default)]
struct Config {
host: String, // 默认 ""
port: u16, // 默认 0
enabled: bool, // 默认 false
}
fn main() {
let config = Config::default();
println!("{:?}", config);
// Config { host: "", port: 0, enabled: false }
// 部分字段使用默认值
let config2 = Config {
host: "localhost".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: "alice".to_string(),
email: "alice@example.com".to_string(),
};
let json = serde_json::to_string(&user).unwrap();
println!("{}", json);
// {"id":1,"username":"alice","email":"alice@example.com"}
// 反序列化
let user2: User = serde_json::from_str(&json).unwrap();
println!("{:?}", user2);
}
常用属性:
#[derive(Serialize, Deserialize)]
pub struct User {
#[serde(rename = "userId")] // JSON 中使用 userId
pub id: i64,
#[serde(skip)] // 跳过该字段
pub password_hash: String,
#[serde(default)] // 反序列化时缺失则使用默认值
pub role: String,
#[serde(skip_serializing_if = "Option::is_none")] // 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::(
"SELECT id, username, email FROM users WHERE id = ?"
)
.bind(id)
.fetch_one(pool)
.await?;
Ok(user)
}
要求:
- 字段名必须与数据库列名匹配(或使用
#[sqlx(rename = "...")]) - 字段类型必须与数据库列类型兼容
3. Error (thiserror)
作用: 简化自定义错误类型的创建。
use thiserror::Error;
#[derive(Error, Debug)]
pub enum JwtError {
#[error("JWT 生成失败")]
EncodingError(#[from] jsonwebtoken::errors::Error),
#[error("JWT 验证失败")]
ValidationError,
#[error("JWT 已过期")]
Expired,
#[error("无效的 JWT: {0}")]
Invalid(String),
}
fn main() {
let err = JwtError::Expired;
println!("{}", err); // JWT 已过期
}
属性说明:
#[error("...")]: 定义错误显示消息#[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("JWT 生成失败")]
EncodingError(#[from] jsonwebtoken::errors::Error),
#[error("JWT 验证失败")]
ValidationError,
#[error("JWT 已过期")]
Expired,
}
组合说明:
Error: thiserror 自动实现 std::error::ErrorDebug: 必需,用于错误传播#[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 = "user_email")] // 映射到不同的列名
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 = "default_host")]
pub host: String,
#[serde(default = "default_port")]
pub port: u16,
}
fn default_host() -> String {
"127.0.0.1".to_string()
}
fn default_port() -> u16 {
3000
}
模式 4: 枚举状态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Status {
Pending,
Active,
Completed,
Failed,
}
模式 5: 结果类型
#[derive(Debug, Serialize)]
#[serde(tag = "status", content = "data")]
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 = "Option::is_none")]
pub next_page: Option,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec,
}
总结
Derive 宏的使用原则:
- 按需选择: 只 derive 实际需要的 trait
- 理解依赖: 注意 trait 之间的依赖关系
- 控制行为: 使用属性精确控制生成代码
- 手动覆盖: 复杂逻辑手动实现
- 性能意识: 理解各个 trait 的性能影响