宏是 Rust 元编程的核心武器。从 vec! 的简洁语法到过程宏的代码生成,宏让你在编译期操作代码本身。但能力越大,陷阱越深——从基础到实战,掌握宏的正确打开方式。
宏和函数看起来都能"复用代码",但它们运作在不同的维度上。函数操作值,宏操作代码。理解这个本质区别,是正确使用宏的第一步。
关键区别:宏在编译期就被展开成 Rust 代码,编译器看到的是展开后的结果,而不是宏调用本身。
Rust 函数不支持可变参数。println!、vec! 能接受任意数量参数,因为宏可以根据模式生成不同的代码。
宏可以生成结构体、impl 块、trait 实现等声明性代码。函数只能生成值和执行逻辑,不能凭空"长出"新的类型定义。
concat!、env!、include_str! 在编译期就完成计算,结果直接嵌入二进制。函数只能运行时计算。
宏可以定义自己的语法规则,构建领域特定语言。如 sqlx::query! 编译期验证 SQL 语法。
⚠️ 黄金法则:如果你能用函数+泛型+trait 解决,就不要用宏。宏是最后的抽象手段,不是第一选择。
声明宏是 Rust 最常见的宏形式。原理类似模式匹配:你定义一系列规则,编译器按照规则把输入 token 替换成输出 token。
macro_rules! say_hello {
() => {
println!("Hello, world!");
};
}
macro_rules! create_string {
($s:expr) => {
String::from($s)
};
}
macro_rules! return_number {
(one) => { 1 };
(two) => { 2 };
(three) => { 3 };
}
say_hello!(); // → println!("Hello, world!");
let s = create_string!("hello"); // → String::from("hello")
let n = return_number!(two); // → 2| 分类器 | 匹配内容 | 示例 |
|---|---|---|
expr | 表达式 | 1 + 2、f()、true |
ident | 标识符 | x、MyStruct |
ty | 类型 | i32、Vec<String> |
path | 路径 | std::collections::HashMap |
pat | 模式 | Some(x)、_ |
literal | 字面量 | 42、"hello" |
tt | token tree | 任何单个 token 或括号组 |
item | 项 | fn foo() {}、struct Bar; |
block | 代码块 | { let x = 1; x } |
meta | 元属性 | derive(Debug) |
stmt | 语句 | let x = 1; |
🔑 tt 是万能钥匙:tt 匹配任何单个 token tree(标识符、标点、字面量,或一对括号及其全部内容)。最灵活但也最不安全——编译器不会帮你做语法检查。
语法:$($var:frag) sep rep,其中 sep 是分隔符(可选),rep 是 * / + / ?。
macro_rules! hashmap {
({ $($key:expr => $val:expr),* $(,)? }) => {{
let mut m = ::std::collections::HashMap::new();
$( m.insert($key, $val); )*
m
}};
}
let scores = hashmap!({
"Alice" => 95,
"Bob" => 87,
"Carol" => 92,
});💡 尾部逗号技巧:$(,)? 允许最后一个元素后面有可选逗号。Rust 宏惯例,方便增删元素时不需要调整逗号。
macro_rules! count {
() => (0);
($first:tt $($rest:tt)*) => (1 + count!($($rest)*));
}
// count!(a, b, c) → 1 + count!(b, c) → 1 + 1 + count!(c) → 1 + 1 + 1 + count!() → 3
macro_rules! impl_from_variants {
// 基础:处理最后一个
($enum:ident, $var:ident($type:ty)) => {
impl From<$type> for $enum {
fn from(v: $type) -> Self { $enum::$var(v) }
}
};
// 递归:处理一个,继续下一个
($enum:ident, $var:ident($type:ty), $( $rv:ident($rt:ty) ),+) => {
impl From<$type> for $enum {
fn from(v: $type) -> Self { $enum::$var(v) }
}
impl_from_variants!($enum, $( $rv($rt) ),+);
};
}fn main() {
let x = 10;
macro_rules! using_var {
() => { println!("x = {}", x) };
}
using_var!(); // ✅ 编译通过,宏展开后能访问外部 x
macro_rules! safe_macro {
() => { let __internal = 999; };
}
let __internal = 1;
safe_macro!();
assert_eq!(__internal, 1); // ✅ 宏内部变量不会污染外部
}# 1. cargo expand — 查看宏展开后的代码(最常用!)
cargo install cargo-expand
cargo expand
# 2. rustc -Z unpretty=expanded
rustc -Z unpretty=expanded src/main.rs
# 3. compile_error! 打印调试信息
macro_rules! debug_macro {
($($tokens:tt)*) => {
compile_error!(stringify!($($tokens)*));
};
}
# 4. 递归深度上限(默认 128)
#![recursion_limit = "256"]标准库中有大量精心设计的宏,拆解它们是学习宏设计的最佳途径。
macro_rules! vec {
() => (::std::vec::Vec::new());
($elem:expr; $n:expr) => (::std::vec::from_elem($elem, $n));
($($x:expr),+ $(,)?) => (
<[_]>::into_vec(Box::new([$($x),+]))
);
}
let v1 = vec![]; // Vec::new()
let v2 = vec![0; 100]; // from_elem(0, 100)
let v3 = vec![1, 2, 3]; // into_vec(Box::new([1,2,3]))🔑 性能细节:vec![1, 2, 3] 先在栈上创建数组 [1, 2, 3],再 Box::new 移到堆上,转为 Vec。大小编译期已知,无需动态扩容。
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => {{
$crate::io::_print($crate::format_args!($($arg)*));
}};
}
// format_args! 是编译器内置宏,编译期验证:
// - {} 数量与参数数量匹配
// - 格式说明符语法正确(:?, :x, :08 等)
// - 参数类型与格式说明符兼容
println!("Name: {}, Age: {}", name, age); // 位置参数
println!("{0} {1} {0}", a, b); // 复用参数
println!("{name} is {age}", name="A", age=30); // 命名参数macro_rules! assert_eq {
($left:expr, $right:expr $(,)?) => {{
match (&$left, &$right) {
(l, r) => {
if !(*l == *r) {
panic!("assertion failed\n left: {:?}\n right: {:?}", l, r);
}
}
}
}};
}
// 关键:先用 & 借用再比较——避免消耗值
let a = String::from("hello");
assert_eq!(a, "hello"); // ✅ a 只是借用,之后还能用macro_rules! matches {
($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => {
match $expression {
$( $pattern )|+ $( if $guard )? => true,
_ => false
}
};
}
enum Value { Int(i32), Str(String), Bool(bool) }
let v = Value::Int(42);
assert!(matches!(v, Value::Int(_)));
assert!(matches!(v, Value::Int(1..=100))); // 范围模式
assert!(!matches!(v, Value::Str(_)));
// 多模式
let x = Some(5);
assert!(matches!(x, Some(1) | Some(5) | Some(10)));// todo! — 标记待实现,运行到此 panic
fn process(data: &[u8]) -> Result<()> {
todo!("handle binary data");
}
// compile_error! — 编译期报错,用于宏条件分支
macro_rules! require_nightly {
() => {
#[cfg(not(feature = "nightly"))]
compile_error!("This feature requires nightly Rust");
};
}// concat! — 编译期字符串拼接
const DB_PATH: &str = concat!("/var/data/", env!("USER"), "/db");
// stringify! — token 转字符串
macro_rules! show_expr {
($e:expr) => { println!("{} = {:?}", stringify!($e), $e) };
}
show_expr!(1 + 2); // 输出: 1 + 2 = 3
// file! / line! — 源码位置
fn log(msg: &str) {
println!("[{}:{}] {}", file!(), line!(), msg);
}
// module_path! — 当前模块路径
mod server {
pub fn identify() {
println!("I am {}", module_path!()); // "crate::server"
}
}过程宏是 Rust 元编程的高端武器。不同于 macro_rules! 的模式匹配替换,过程宏直接操作 token 流——你自己写 Rust 代码来生成 Rust 代码。
# 过程宏必须在单独的 crate 中
my_macro_lib/
├── Cargo.toml
│ [lib]
│ proc-macro = true
│
│ [dependencies]
│ syn = "2"
│ quote = "1"
│ proc-macro2 = "1"
│
└── src/
└── lib.rs| 库 | 作用 | 比喻 |
|---|---|---|
syn | TokenStream → Rust AST | 阅读理解:文本变结构 |
quote | 代码模板 → TokenStream | 写作:结构变代码 |
proc-macro2 | 可测试版本 | 草稿纸:单元测试 |
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 ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen.into()
}
// 使用方
use my_macro_lib::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
Pancakes::hello_macro(); // Hello, Macro! My name is Pancakes!#[proc_macro_derive(Builder, attributes(builder))]
pub fn builder_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
// attributes(builder) 允许宏识别 #[builder(...)] 属性
impl_builder(&ast)
.unwrap_or_else(|e| e.to_compile_error().into())
}
fn impl_builder(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) =
ast.generics.split_for_impl();
let fields = match &ast.data {
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => &fields.named,
_ => return Err(syn::Error::new_spanned(
ast, "Builder only supports structs with named fields"
)),
};
// 生成 Builder 代码...
let builder_name = format_ident!("{}Builder", name);
let field_names: Vec<_> = fields.iter()
.map(|f| &f.ident)
.collect();
let field_types: Vec<_> = fields.iter()
.map(|f| &f.ty)
.collect();
let expanded = quote! {
pub struct #builder_name #impl_generics #where_clause {
#(
#field_names: Option<#field_types>,
)*
}
impl #impl_generics #name #ty_generics #where_clause {
pub fn builder() -> #builder_name #ty_generics {
#builder_name {
#( #field_names: None, )*
}
}
}
};
Ok(expanded.into())
}#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// attr: 属性参数 → "/api/users"
// item: 修饰的项 → fn get_users() { ... }
let path = parse_macro_input!(attr as LitStr);
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let expanded = quote! {
#func
::my_framework::register_route(#path, #func_name);
};
expanded.into()
}
// 使用
#[route("/api/users")]
fn get_users() -> Json<Vec<User>> { ... }#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let query = parse_macro_input!(input as LitStr);
// 编译期验证 SQL 语法、检查表名和列名
validate_sql(&query.value())
.map_err(|e| syn::Error::new_spanned(&query, e))
.unwrap_or_else(|e| e.to_compile_error().into());
quote! {
#query
}.into()
}
// 使用
let query = sql!("SELECT * FROM users WHERE id = $1");Builder 模式在 Rust 中极其常见,但手写非常繁琐。Derive 宏可以自动生成全部样板代码。
use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{parse_macro_input, DeriveInput, Data, Fields};
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let builder_name = format_ident!("{}Builder", name);
let fields = match &ast.data {
Data::Struct(data) => match &data.fields {
Fields::Named(ref f) => &f.named,
_ => panic!("Only named fields supported"),
},
_ => panic!("Only structs supported"),
};
let field_names: Vec<_> = fields.iter().map(|f| &f.ident).collect();
let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();
let expanded = quote! {
pub struct #builder_name {
#( #field_names: Option<#field_types>, )*
}
impl #name {
pub fn builder() -> #builder_name {
#builder_name {
#( #field_names: None, )*
}
}
}
impl #builder_name {
#(
pub fn #field_names(mut self, val: #field_types) -> Self {
self.#field_names = Some(val);
self
}
)*
pub fn build(self) -> Result<#name, String> {
Ok(#name {
#( #field_names: self.#field_names
.ok_or(format!("field {} is required", stringify!(#field_names)))?, )*
})
}
}
};
expanded.into()
}
// 使用方
#[derive(Builder)]
struct User {
name: String,
age: u32,
email: String,
}
let user = User::builder()
.name("Alice".into())
.age(30)
.email("alice@example.com".into())
.build()?;#[proc_macro_derive(Validate, attributes(validate))]
pub fn derive_validate(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let fields = get_named_fields(&ast);
let validations = fields.iter().map(|f| {
let fname = &f.ident;
let mut checks = Vec::new();
for attr in f.attrs.iter().filter(|a| a.path().is_ident("validate")) {
// 解析 #[validate(length(min = 1, max = 100))]
// 解析 #[validate(range(min = 0, max = 150))]
// 解析 #[validate(email))]
// 解析 #[validate(custom = "validate_username"))]
}
quote! {
#( #checks )*
}
});
let expanded = quote! {
impl Validate for #name {
fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
#( #validations )*
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
}
};
expanded.into()
}
// 使用
#[derive(Validate)]
struct SignupForm {
#[validate(length(min = 1, max = 50))]
username: String,
#[validate(email)]
email: String,
#[validate(range(min = 0, max = 150))]
age: u32,
}
form.validate()?;💡 设计原则:Derive 宏生成的代码应该是"如果手写会怎么写"的模板化版本。先手写一次完整的实现,再抽象成宏,比直接写宏容易得多。
Attribute 宏是最灵活的过程宏类型。它可以修饰任意 item(函数、结构体、模块等),完全控制生成什么代码。
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, ReturnType};
#[proc_macro_attribute]
pub fn async_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let func_body = &func.block;
let func_inputs = &func.sig.inputs;
let expanded = quote! {
fn #func_name() {
async fn inner(#func_inputs) #func_body
tokio::runtime::Runtime::new()
.unwrap()
.block_on(inner())
}
};
expanded.into()
}
// 使用:把 async fn 变成同步入口
#[async_main]
async fn main() {
let resp = reqwest::get("https://example.com").await?;
println!("{}", resp.text().await?);
}| 库 | 宏 | 作用 |
|---|---|---|
| tokio | #[tokio::main] | 自动包装 async fn 为 runtime 入口 |
| serde | #[serde(rename_all = "camelCase")] | 控制序列化字段名格式 |
| derive_more | #[derive(Display)] | 自动生成 Display 等常用 trait |
| tracing | #[tracing::instrument] | 自动为函数添加 tracing span |
| pyo3 | #[pyfunction] | 将 Rust 函数导出为 Python 函数 |
| rocket | #[get("/path")] | HTTP 路由注册 |
| sqlx | #[sqlx::test] | 自动设置测试数据库 |
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Token, LitStr};
struct RouteArgs {
method: Ident,
path: LitStr,
}
impl Parse for RouteArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let method: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let path: LitStr = input.parse()?;
Ok(RouteArgs { method, path })
}
}
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as RouteArgs);
let func = parse_macro_input!(item as ItemFn);
let method = &args.method;
let path = &args.path;
let func_name = &func.sig.ident;
let expanded = quote! {
#func
::my_framework::routes::register(
::my_framework::Method::#method,
#path,
#func_name,
);
};
expanded.into()
}宏最常见的"杀手级"应用是构建 DSL(领域特定语言),让代码表达更贴近问题域。
macro_rules! describe {
($name:expr, { $($body:tt)* }) => {
mod test_module {
use super::*;
$($body)*
}
};
}
macro_rules! it {
($desc:expr, $body:expr) => {
#[test]
fn it() { $body }
};
}
describe!("Vec operations", {
it!("pushes and pops", {
let mut v = vec![1, 2];
v.push(3);
assert_eq!(v.pop(), Some(3));
});
it!("has correct length", {
let v = vec![1, 2, 3];
assert_eq!(v.len(), 3);
});
});macro_rules! impl_display_for_enum {
($enum:ident { $($variant:ident),+ $(,)? }) => {
impl std::fmt::Display for $enum {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
$( $enum::$variant => write!(f, stringify!($variant)), )+
}
}
}
};
}
enum Color { Red, Green, Blue }
impl_display_for_enum!(Color { Red, Green, Blue });
assert_eq!(Color::Red.to_string(), "Red");// 定义路由表
macro_rules! define_routes {
($($method:ident $path:literal => $handler:ident),+ $(,)?) => {
pub fn setup_routes(app: &mut App) {
$(
app.route($path, RouteMethod::$method, $handler);
)+
}
pub fn route_count() -> usize {
[+ $( 1 )* ] .len() // 编译期计数
}
};
}
define_routes! {
GET "/" => index,
GET "/users" => list_users,
POST "/users" => create_user,
GET "/users/:id" => get_user,
PUT "/users/:id" => update_user,
DELETE "/users/:id" => delete_user,
}// 为多个类型实现相同逻辑
macro_rules! impl_numeric_ops {
($($type:ty),*) => {
$(
impl MyOps for $type {
fn double(&self) -> Self { *self * 2 }
fn squared(&self) -> Self { *self * *self }
fn is_positive(&self) -> bool { *self > 0 }
}
)*
};
}
impl_numeric_ops!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64);
// 新类型模式 + Delegation
macro_rules! delegate {
($type:ident, $inner:ty, $trait:ident $(, $method:ident)*) => {
impl $trait for $type {
$( fn $method(&self) -> <$inner as $trait>::Output {
self.0.$method()
} )*
}
};
}宏是双刃剑。了解常见陷阱,才能避开它们。
// 宏展开前的代码
let v = hashmap!({
"name" => 42, // ← 类型错误:期望 String,得到 i32
});
// 编译器报错可能指向宏定义内部,而不是你的调用处
// error[E0277]: the trait bound `i32: Into` is not satisfied
// --> src/macros.rs:5:12 ← 指向宏定义,而非调用处!
// ✅ 改进:在宏中加入更好的错误提示
macro_rules! hashmap {
({ $($key:expr => $val:expr),* $(,)? }) => {{
let mut m = ::std::collections::HashMap::new();
$(
m.insert(
$key,
// 显式标注类型,让错误更清晰
{ let v: _ = $val; v }
);
)*
m
}};
} 🚨 最大痛点:宏展开后的代码看不到,出了问题只能靠猜。必须使用 cargo expand 查看实际生成的代码。过程宏中用 syn::Error::new_spanned 提供精确的错误位置。
// ❌ 每次调用都生成完整代码
macro_rules! big_impl {
($name:ident) => {
impl $name {
fn method1(&self) { /* 50 行 */ }
fn method2(&self) { /* 50 行 */ }
fn method3(&self) { /* 50 行 */ }
// ... 更多方法
}
};
}
big_impl!(TypeA);
big_impl!(TypeB);
big_impl!(TypeC); // 生成 3 × N 行代码!
// ✅ 改进:用泛型 + trait 减少膨胀
trait BigOps {
fn method1(&self);
fn method2(&self);
fn method3(&self);
}
impl<T: Common> BigOps for T {
fn method1(&self) { /* 只有一份实现 */ }
// ...
}| 维度 | 声明宏 (macro_rules!) | 过程宏 |
|---|---|---|
| 学习曲线 | 低 模式匹配 | 高 syn/quote 生态 |
| 编译速度 | 快 简单替换 | 慢 需要编译 proc-macro crate |
| 调试难度 | 中 cargo expand 可查 | 高 需要单元测试 |
| 错误信息 | 差 指向宏定义 | 中 可自定义 span |
| 可测试性 | 差 只能测试展开结果 | 好 proc-macro2 可单元测试 |
| 能力 | 模式匹配+替换 | 完全控制 AST |
| 依赖 | 无 | syn + quote + proc-macro2 |
| 适用场景 | 简单 DSL、样板消除 | derive、attribute、复杂代码生成 |
⚠️ 编译时间影响:过程宏需要编译独立的 crate,首次编译慢。但增量编译时,只有宏定义变更才重新编译。声明宏零额外编译成本。如果你的宏逻辑很简单,声明宏是更好的选择。
最后,我们来实现一个完整的案例——用 Derive 过程宏自动生成 ORM 字段映射代码。这是过程宏最典型的应用场景之一。
// 用宏标注
#[derive(OrmModel)]
struct User {
#[orm(primary_key)]
id: i64,
#[orm(column = "user_name")]
name: String,
#[orm(default)]
email: Option<String>,
created_at: chrono::NaiveDateTime,
}
// 自动生成:
// 1. 表名映射
// 2. 列名映射
// 3. INSERT 语句生成
// 4. SELECT 结果解析
// 5. UPDATE 语句生成
User::table_name(); // "users"
User::column_names(); // ["id", "user_name", "email", "created_at"]
User::insert_sql(); // "INSERT INTO users (id, user_name, ...) VALUES ($1, $2, ...)"
User::from_row(&row)?; // 从 sqlx::PgRow 解析为 Userorm-macro/
├── orm-derive/ # 过程宏 crate
│ ├── Cargo.toml
│ └── src/lib.rs
├── orm-core/ # 运行时支持
│ ├── Cargo.toml
│ └── src/lib.rs
└── orm-tests/ # 集成测试
├── Cargo.toml
└── src/main.rsuse proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{
parse_macro_input, DeriveInput, Data, Fields, Attribute,
Lit, Meta, NestedMeta, Result, Error,
};
#[proc_macro_derive(OrmModel, attributes(orm))]
pub fn derive_orm_model(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
match derive_orm_model_impl(&ast) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}
struct FieldInfo {
ident: proc_macro2::Ident,
ty: proc_macro2::Type,
column_name: String,
is_primary_key: bool,
has_default: bool,
}
fn parse_orm_attrs(attrs: &[Attribute]) -> (Option<String>, bool, bool) {
let mut column_name = None;
let mut is_pk = false;
let mut has_default = false;
for attr in attrs.iter().filter(|a| a.path.is_ident("orm")) {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("primary_key") {
is_pk = true;
} else if meta.path.is_ident("column") {
let v: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = v {
column_name = Some(s.value());
}
} else if meta.path.is_ident("default") {
has_default = true;
}
Ok(())
});
}
(column_name, is_pk, has_default)
}
fn derive_orm_model_impl(ast: &DeriveInput) -> Result<proc_macro2::TokenStream> {
let struct_name = &ast.ident;
let table_name = to_snake_case(&struct_name.to_string()) + "s";
let fields = match &ast.data {
Data::Struct(data) => match &data.fields {
Fields::Named(ref f) => &f.named,
_ => return Err(Error::new_spanned(ast, "Only named fields supported")),
},
_ => return Err(Error::new_spanned(ast, "Only structs supported")),
};
let field_infos: Vec<FieldInfo> = fields.iter().map(|f| {
let (col, is_pk, has_default) = parse_orm_attrs(&f.attrs);
let column_name = col.unwrap_or_else(|| to_snake_case(&f.ident.as_ref().unwrap().to_string()));
FieldInfo {
ident: f.ident.clone().unwrap(),
ty: f.ty.clone(),
column_name,
is_primary_key: is_pk,
has_default,
}
}).collect();
// 表名
let table_name_lit = &table_name;
// 列名
let col_names: Vec<_> = field_infos.iter().map(|f| &f.column_name).collect();
// 字段标识
let field_idents: Vec<_> = field_infos.iter().map(|f| &f.ident).collect();
// INSERT: 非默认字段
let insert_fields: Vec<_> = field_infos.iter()
.filter(|f| !f.has_default)
.collect();
let insert_col_names: Vec<_> = insert_fields.iter().map(|f| &f.column_name).collect();
let insert_idents: Vec<_> = insert_fields.iter().map(|f| &f.ident).collect();
let insert_placeholders: Vec<String> = (1..=insert_fields.len())
.map(|i| format!("${}", i))
.collect();
// UPDATE: 非主键字段
let update_fields: Vec<_> = field_infos.iter()
.filter(|f| !f.is_primary_key)
.collect();
let update_col_names: Vec<_> = update_fields.iter().map(|f| &f.column_name).collect();
let update_idents: Vec<_> = update_fields.iter().map(|f| &f.ident).collect();
// 主键
let pk_field = field_infos.iter().find(|f| f.is_primary_key);
let pk_ident = pk_field.map(|f| &f.ident);
let pk_col = pk_field.map(|f| &f.column_name.as_str()).unwrap_or("id");
let expanded = quote! {
impl OrmModel for #struct_name {
fn table_name() -> &'static str {
#table_name_lit
}
fn column_names() -> Vec<&'static str> {
vec![ #(#col_names),* ]
}
fn insert_sql() -> String {
format!(
"INSERT INTO {} ({}) VALUES ({}) RETURNING *",
#table_name_lit,
#(#insert_col_names),*,
#(#insert_placeholders),*
)
}
fn update_sql() -> String {
let sets: Vec<String> = vec![
#( format!("{} = ${}", #update_col_names, 1 + {}), )*
];
format!(
"UPDATE {} SET {} WHERE {} = $1",
#table_name_lit,
sets.join(", "),
#pk_col,
)
}
fn values(&self) -> Vec<&(dyn ::sqlx::Encode<'_, ::sqlx::Postgres> + ::sqlx::Type<::sqlx::Postgres>)> {
vec![ #( &self.#insert_idents as _ ),* ]
}
}
};
Ok(expanded)
}
fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 { result.push('_'); }
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
result
}use sqlx::{FromRow, PgConnection, Postgres, QueryBuilder};
pub trait OrmModel: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Sized {
fn table_name() -> &'static str;
fn column_names() -> Vec<&'static str>;
fn insert_sql() -> String;
fn update_sql() -> String;
async fn insert(&self, pool: &sqlx::PgPool) -> sqlx::Result<Self> {
let sql = Self::insert_sql();
// 用 sqlx::query_as 执行...
todo!()
}
async fn find_by_id(pool: &sqlx::PgPool, id: i64) -> sqlx::Result<Option<Self>> {
let sql = format!(
"SELECT * FROM {} WHERE id = $1",
Self::table_name()
);
sqlx::query_as::<_, Self>(&sql)
.bind(id)
.fetch_optional(pool)
.await
}
async fn find_all(pool: &sqlx::PgPool) -> sqlx::Result<Vec<Self>> {
let sql = format!("SELECT * FROM {}", Self::table_name());
sqlx::query_as::<_, Self>(&sql)
.fetch_all(pool)
.await
}
}use orm_core::OrmModel;
use orm_derive::OrmModel;
use sqlx::FromRow;
#[derive(OrmModel, FromRow)]
struct User {
#[orm(primary_key)]
id: i64,
#[orm(column = "user_name")]
name: String,
#[orm(default)]
email: Option<String>,
created_at: chrono::NaiveDateTime,
}
async fn example(pool: &sqlx::PgPool) -> sqlx::Result<()> {
// 查询
let user = User::find_by_id(&pool, 1).await?;
// 查询全部
let all_users = User::find_all(&pool).await?;
// 表名和列名
println!("Table: {}", User::table_name()); // "users"
println!("Columns: {:?}", User::column_names()); // ["id", "user_name", "email", "created_at"]
Ok(())
}💡 扩展思路:这个 ORM 宏可以继续扩展——支持 #[orm(relation = "orders")] 自动生成关联查询、支持 #[orm(index)] 自动创建迁移脚本、支持 #[orm(validate = "check_email")] 编译期验证等。过程宏的能力边界,就是你的想象力边界。
1. 错误处理:每个字段属性解析都应该有精确的错误位置和友好提示。
2. 泛型支持:struct User<T> { data: T } 需要正确传递泛型参数和 where 子句。
3. 生命周期:含引用字段的结构体需要正确处理生命周期。
4. 可选字段:Option<T> 字段映射为 NULL,需要特殊处理。
5. 类型映射:Rust 类型到 SQL 类型的映射表需要可扩展。
6. 异步运行时:支持 tokio / async-std 不同运行时。
7. 连接池:支持不同数据库连接池实现。