Rust宏编程完全指南:用元编程解锁Rust的终极力量

作者:土豆1250日期:2026/3/17

"宏就像是编译器的魔法棒,挥一挥,重复的代码就消失了。" —— 某位深夜 debug 的 Rustacean

目录

  • Why:为什么需要宏?
  • What:宏是什么?
  • How:如何使用宏?
    • 声明宏 (macro_rules!)
    • 派生宏 (Derive Macros)
    • 属性宏 (Attribute Macros)
    • 函数式宏 (Function-like Macros)
  • 最佳实践
  • 常见误区
  • 总结

Why:为什么需要宏?

想象一下,你正在写一个 Web 框架,需要为 50 个不同的结构体实现相同的序列化逻辑。如果用函数,你会发现自己陷入了"复制粘贴地狱"。如果用泛型,又会因为 Rust 严格的类型系统而撞墙。这时候,宏就像超级英雄一样闪亮登场了!

宏解决的核心问题

1. 消除重复代码(DRY 原则的终极武器)

1// 没有宏之前,你可能要写:
2impl ToString for User { /* ... */ }
3impl ToString for Product { /* ... */ }
4impl ToString for Order { /* ... */ }
5// 写到第 50 个的时候,你已经怀疑人生了
6
7// 有了宏:
8#[derive(ToString)]
9struct User { /* ... */ }
10// 搞定!
11

2. 编译时计算(零运行时开销)

宏在编译期展开,这意味着:

  • 没有运行时性能损失
  • 错误在编译时就能发现
  • 生成的代码可以被编译器完全优化

3. 创建 DSL(领域特定语言)

1// 比如 vec! 宏让你写出这样优雅的代码:
2let v = vec![1, 2, 3, 4, 5];
3
4// 而不是:
5let mut v = Vec::new();
6v.push(1);
7v.push(2);
8v.push(3);
9v.push(4);
10v.push(5);
11

4. 超越泛型的抽象能力

泛型很强大,但它有局限:

  • 泛型要求类型在运行时已知
  • 泛型受 trait bounds 约束
  • 宏可以操作任意语法,甚至可以生成新的类型

What:宏是什么?

在 Rust 中,宏是编写生成代码的代码(元编程)。但与 C 语言的简单文本替换不同,Rust 的宏操作的是抽象语法树(AST),这让它们既强大又安全。

Rust 宏的家族树

1Rust 
2├── 声明宏 (Declarative Macros)
3   └── macro_rules! - 基于模式匹配的宏
4
5└── 过程宏 (Procedural Macros)
6    ├── 派生宏 (Derive Macros) - #[derive(Trait)]
7    ├── 属性宏 (Attribute Macros) - #[attribute]
8    └── 函数式宏 (Function-like Macros) - macro!(...)
9

核心概念:TokenStream

宏的输入和输出都是 TokenStream(标记流),它是 Rust 代码的抽象表示:

1// 这段代码对编译器来说是一个 TokenStream
2let x: i32 = 42;
3
4// 分解成 tokens:
5// [let] [x] [:] [i32] [=] [42] [;]
6

How:如何使用宏?

1. 声明宏 (macro_rules!)

声明宏是最容易入门的宏类型,它使用模式匹配语法,就像一个超级版的 match 表达式。

1.1 基础语法

1macro_rules! say_hello {
2    // () 表示宏不接受参数
3    () => {
4        println!("Hello, Rustacean!");
5    };
6}
7
8fn main() {
9    say_hello!(); // 输出: Hello, Rustacean!
10}
11

1.2 接受参数

宏使用片段说明符(fragment specifiers)来匹配不同类型的语法元素:

说明符匹配内容示例
expr表达式1 + 2, foo()
ident标识符x, my_var
ty类型i32, Vec<String>
pat模式Some(x), _
stmt语句let x = 5;
block代码块{ /* ... */ }
itemfn foo() {}, struct Bar;
ttToken树任何标记

实例:创建一个 HashMap 初始化宏

1macro_rules! hashmap {
2    // 匹配 key => value 对,逗号分隔
3    ($($key:expr => $value:expr),* $(,)?) => {
4        {
5            let mut map = ::std::collections::HashMap::new();
6            $(
7                map.insert($key, $value);
8            )*
9            map
10        }
11    };
12}
13
14fn main() {
15    let scores = hashmap! {
16        "Alice" => 95,
17        "Bob" => 87,
18        "Charlie" => 92,
19    };
20    
21    println!("{:?}", scores);
22}
23

语法分析:

  • $( ... )* - 零次或多次重复
  • $(,)? - 可选的尾随逗号
  • $key:expr - 捕获一个表达式并命名为 key

1.3 多模式匹配

1macro_rules! calculate {
2    // 加法
3    (add $a:expr, $b:expr) => {
4        $a + $b
5    };
6    
7    // 乘法
8    (mul $a:expr, $b:expr) => {
9        $a * $b
10    };
11    
12    // 多个数字求和
13    (sum $($num:expr),+) => {
14        {
15            let mut total = 0;
16            $(
17                total += $num;
18            )+
19            total
20        }
21    };
22}
23
24fn main() {
25    println!("{}", calculate!(add 10, 20));        // 30
26    println!("{}", calculate!(mul 5, 6));          // 30
27    println!("{}", calculate!(sum 1, 2, 3, 4, 5)); // 15
28}
29

1.4 实战案例:类型安全的单位转换

1macro_rules! unit_converter {
2    ($value:expr, $from:ident to $to:ident) => {
3        match (stringify!($from), stringify!($to)) {
4            ("meters", "feet") => $value * 3.28084,
5            ("feet", "meters") => $value / 3.28084,
6            ("kg", "pounds") => $value * 2.20462,
7            ("pounds", "kg") => $value / 2.20462,
8            _ => panic!("不支持的单位转换: {} to {}", 
9                       stringify!($from), stringify!($to)),
10        }
11    };
12}
13
14fn main() {
15    let height_m = 1.75;
16    let height_ft = unit_converter!(height_m, meters to feet);
17    println!("{}米 = {:.2}英尺", height_m, height_ft);
18}
19

2. 派生宏 (Derive Macros)

派生宏是最常用的过程宏类型,你肯定用过 #[derive(Debug, Clone)]

2.1 工作原理

派生宏:

  1. 接收结构体/枚举的 TokenStream
  2. 分析其字段和类型
  3. 生成 trait 实现代码
  4. 不修改原始类型,只添加新代码

2.2 创建自定义派生宏

步骤 1:创建过程宏 crate

1# Cargo.toml
2[package]
3name = "my_derive"
4version = "0.1.0"
5edition = "2021"
6
7[lib]
8proc-macro = true  # 关键:声明这是过程宏 crate
9
10[dependencies]
11syn = { version = "2.0", features = ["full"] }
12quote = "1.0"
13proc-macro2 = "1.0"
14

步骤 2:实现派生宏

1// src/lib.rs
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, DeriveInput};
5
6#[proc_macro_derive(Builder)]
7pub fn derive_builder(input: TokenStream) -> TokenStream {
8    // 解析输入的结构体
9    let input = parse_macro_input!(input as DeriveInput);
10    let name = &input.ident;
11    let builder_name = syn::Ident::new(
12        &format!("{}Builder", name), 
13        name.span()
14    );
15    
16    // 提取字段
17    let fields = if let syn::Data::Struct(data) = &input.data {
18        if let syn::Fields::Named(fields) = &data.fields {
19            &fields.named
20        } else {
21            panic!("Builder 只支持命名字段的结构体");
22        }
23    } else {
24        panic!("Builder 只能应用于结构体");
25    };
26    
27    // 生成 builder 字段(都是 Option)
28    let builder_fields = fields.iter().map(|f| {
29        let name = &f.ident;
30        let ty = &f.ty;
31        quote! {
32            #name: Option<#ty>
33        }
34    });
35    
36    // 生成 setter 方法
37    let setters = fields.iter().map(|f| {
38        let name = &f.ident;
39        let ty = &f.ty;
40        quote! {
41            pub fn #name(mut self, #name: #ty) -> Self {
42                self.#name = Some(#name);
43                self
44            }
45        }
46    });
47    
48    // 生成 build 方法
49    let build_fields = fields.iter().map(|f| {
50        let name = &f.ident;
51        quote! {
52            #name: self.#name.ok_or(
53                concat!("字段 ", stringify!(#name), " 未设置")
54            )?
55        }
56    });
57    
58    // 使用 quote! 生成最终代码
59    let expanded = quote! {
60        pub struct #builder_name {
61            #(#builder_fields,)*
62        }
63        
64        impl #builder_name {
65            #(#setters)*
66            
67            pub fn build(self) -> Result<#name, &'static str> {
68                Ok(#name {
69                    #(#build_fields,)*
70                })
71            }
72        }
73        
74        impl #name {
75            pub fn builder() -> #builder_name {
76                #builder_name {
77                    #(#name: None,)*
78                }
79            }
80        }
81    };
82    
83    TokenStream::from(expanded)
84}
85

步骤 3:使用派生宏

1use my_derive::Builder;
2
3#[derive(Builder)]
4struct User {
5    username: String,
6    email: String,
7    age: u32,
8}
9
10fn main() {
11    let user = User::builder()
12        .username("rustacean".to_string())
13        .email("rust@example.com".to_string())
14        .age(25)
15        .build()
16        .unwrap();
17        
18    println!("{} ({}岁): {}", user.username, user.age, user.email);
19}
20

2.3 处理泛型和生命周期

派生宏的一个常见陷阱是忘记处理泛型参数:

1// 这个结构体有泛型参数
2#[derive(Builder)]
3struct Container<T> {
4    value: T,
5}
6
7// 你的派生宏需要提取并保留泛型信息
8let generics = &input.generics;
9let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
10
11let expanded = quote! {
12    impl #impl_generics #name #ty_generics #where_clause {
13        // ...
14    }
15};
16

3. 属性宏 (Attribute Macros)

属性宏可以附加到几乎任何 Rust 项上,并且可以修改或增强该项。

3.1 基础结构

1#[proc_macro_attribute]
2pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
3    // attr: 属性参数(如 #[route(GET, "/users")] 中的 GET, "/users")
4    // item: 被装饰的项(如函数定义)
5    // 返回:替换后的代码
6}
7

3.2 实战:创建一个计时宏

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, ItemFn};
4
5#[proc_macro_attribute]
6pub fn timed(_attr: TokenStream, item: TokenStream) -> TokenStream {
7    let input = parse_macro_input!(item as ItemFn);
8    
9    let fn_name = &input.sig.ident;
10    let fn_block = &input.block;
11    let fn_sig = &input.sig;
12    let fn_vis = &input.vis;
13    
14    let output = quote! {
15        #fn_vis #fn_sig {
16            let _start = std::time::Instant::now();
17            
18            // 执行原始函数体
19            let result = (|| #fn_block)();
20            
21            let _duration = _start.elapsed();
22            println!("函数 {} 执行耗时: {:?}", 
23                     stringify!(#fn_name), _duration);
24            
25            result
26        }
27    };
28    
29    TokenStream::from(output)
30}
31

使用示例:

1#[timed]
2fn slow_computation() -> u64 {
3    std::thread::sleep(std::time::Duration::from_secs(2));
4    42
5}
6
7fn main() {
8    let result = slow_computation();
9    // 输出: 函数 slow_computation 执行耗时: 2.00s
10    println!("结果: {}", result);
11}
12

3.3 高级案例:带参数的属性宏

1#[proc_macro_attribute]
2pub fn cache(attr: TokenStream, item: TokenStream) -> TokenStream {
3    let cache_size: usize = syn::parse_str(&attr.to_string())
4        .expect("cache 参数必须是数字");
5    
6    let input = parse_macro_input!(item as ItemFn);
7    
8    // 生成带缓存功能的函数
9    let expanded = quote! {
10        // 使用 lazy_static 创建全局缓存
11        lazy_static::lazy_static! {
12            static ref CACHE: std::sync::Mutex<
13                lru::LruCache<String, String>
14            > = std::sync::Mutex::new(
15                lru::LruCache::new(
16                    std::num::NonZeroUsize::new(#cache_size).unwrap()
17                )
18            );
19        }
20        
21        #input  // 保留原函数
22        
23        // 生成带缓存的包装函数
24        // ... (实现细节)
25    };
26    
27    TokenStream::from(expanded)
28}
29

使用:

1#[cache(100)]  // 缓存最多 100 个结果
2fn expensive_api_call(query: &str) -> String {
3    // 昂贵的计算...
4}
5

4. 函数式宏 (Function-like Macros)

函数式宏看起来像函数调用,但实际上是在编译时展开。

4.1 基础示例

1#[proc_macro]
2pub fn sql(input: TokenStream) -> TokenStream {
3    let query = input.to_string();
4    
5    // 在编译时验证 SQL 语法!
6    validate_sql(&query).expect("无效的 SQL 语句");
7    
8    // 生成优化的查询执行代码
9    let expanded = quote! {
10        {
11            let query = #query;
12            execute_query(query)
13        }
14    };
15    
16    TokenStream::from(expanded)
17}
18

使用:

1fn main() {
2    let users = sql!(SELECT * FROM users WHERE age > 18);
3    // 如果 SQL 语法错误,编译就会失败!
4}
5

4.2 实战:HTML 模板宏

1macro_rules! html {
2    // 匹配单个标签
3    (<$tag:ident>$($content:tt)*</$close:ident>) => {
4        {
5            assert_eq!(
6                stringify!($tag), 
7                stringify!($close), 
8                "标签不匹配"
9            );
10            format!("<{}>{}</{}>", 
11                    stringify!($tag), 
12                    html!($($content)*), 
13                    stringify!($tag))
14        }
15    };
16    
17    // 匹配文本内容
18    ($text:expr) => {
19        $text.to_string()
20    };
21}
22
23fn main() {
24    let page = html!(
25        <html>
26            <body>
27                <h1>"欢迎来到 Rust 世界!"</h1>
28            </body>
29        </html>
30    );
31    
32    println!("{}", page);
33}
34

最佳实践

1. 命名规范

1//  好的命名
2macro_rules! create_user { /* ... */ }        // 清晰的动词
3#[derive(Serialize)]                          // 名词形式的 trait
4#[validate(email)]                            // 描述性的属性
5
6//  避免
7macro_rules! x { /* ... */ }                  // 太短
8#[do_stuff]                                   // 不明确
9

2. 错误处理

1//  提供有用的错误信息
2macro_rules! require_fields {
3    ($struct_name:ident { $($field:ident),* }) => {
4        compile_error!(concat!(
5            "结构体 ", 
6            stringify!($struct_name), 
7            " 缺少必需字段: ",
8            $(stringify!($field), ", "),*
9        ));
10    };
11}
12
13//  在过程宏中使用 panic!  syn::Error
14#[proc_macro_derive(MyMacro)]
15pub fn my_macro(input: TokenStream) -> TokenStream {
16    let input = match syn::parse(input) {
17        Ok(syntax_tree) => syntax_tree,
18        Err(err) => return err.to_compile_error().into(),
19    };
20    // ...
21}
22

3. 卫生性(Hygiene)

过程宏是非卫生的(unhygienic),这意味着它们可能与周围代码发生命名冲突:

1//  危险:可能与用户代码冲突
2quote! {
3    let result = compute();
4}
5
6//  使用绝对路径
7quote! {
8    let __internal_result = ::my_crate::compute();
9}
10
11//  或使用 fully qualified syntax
12quote! {
13    let result = <Type as Trait>::method();
14}
15

4. 文档化你的宏

1/// 创建一个 HashMap 并初始化键值对
2///
3/// # 示例
4///
5/// ```
6/// let map = hashmap! {
7///     "key1" => "value1",
8///     "key2" => "value2",
9/// };
10/// ```
11#[macro_export]
12macro_rules! hashmap {
13    // ...
14}
15

5. 测试宏

1#[cfg(test)]
2mod tests {
3    use super::*;
4    
5    #[test]
6    fn test_hashmap_macro() {
7        let map = hashmap! {
8            "a" => 1,
9            "b" => 2,
10        };
11        assert_eq!(map.get("a"), Some(&1));
12    }
13    
14    // 使用 trybuild 测试编译错误
15    #[test]
16    fn test_compile_errors() {
17        let t = trybuild::TestCases::new();
18        t.compile_fail("tests/invalid_usage.rs");
19    }
20}
21

6. 性能考虑

1//  宏在编译时展开,没有运行时开销
2let v = vec![1, 2, 3, 4, 5];
3
4//  但要注意宏展开可能增加二进制大小
5// 如果宏在很多地方调用且生成大量代码,考虑改用函数
6

常见误区

误区 1:宏可以做任何事

现实: 宏有明确的限制。

1//  无法生成新的标识符名称(除非使用 paste 等技巧)
2macro_rules! create_function {
3    ($name:ident) => {
4        fn $name_generated() {}  // 错误!不能拼接标识符
5    };
6}
7
8//  需要使用 paste! crate
9use paste::paste;
10
11macro_rules! create_function {
12    ($prefix:ident) => {
13        paste! {
14            fn [<$prefix _generated>]( "<$prefix _generated>") {}  // OK!
15        }
16    };
17}
18

误区 2:忽略宏的顺序依赖

1//  错误:宏还未定义
2fn main() {
3    my_macro!();
4}
5
6macro_rules! my_macro {
7    () => { println!("Hello!"); };
8}
9
10//  正确:先定义宏
11macro_rules! my_macro {
12    () => { println!("Hello!"); };
13}
14
15fn main() {
16    my_macro!();
17}
18

误区 3:过度使用宏

问题代码:

1//  这个可以用简单函数实现
2macro_rules! add_two {
3    ($x:expr) => {
4        $x + 2
5    };
6}
7
8//  更好的选择
9const fn add_two(x: i32) -> i32 {
10    x + 2
11}
12

何时使用宏 vs 函数:

使用宏使用函数
需要操作语法(如生成 trait 实现)简单的值计算
需要在编译时验证某些条件运行时逻辑
需要可变数量的参数类型固定的参数类型
实现 DSL普通业务逻辑

误区 4:不处理边界情况

1//  没有处理空列表
2macro_rules! first {
3    ($($x:expr),+) => {
4        $x  // 错误:哪个 $x?
5    };
6}
7
8//  明确处理
9macro_rules! first {
10    ($first:expr $(, $rest:expr)*) => {
11        $first
12    };
13}
14

误区 5:忘记递归限制

1//  可能触发递归限制
2macro_rules! recurse {
3    () => {
4        recurse!()  // 无限递归!
5    };
6}
7
8//  如果需要深层递归,增加限制
9#![recursion_limit = "256"]
10

调试技巧

1. 查看宏展开结果

1# 使用 cargo expand(需要先安装)
2cargo install cargo-expand
3
4# 查看完整的宏展开
5cargo expand
6
7# 查看特定模块
8cargo expand module_name
9

2. 使用 dbg!

1macro_rules! debug_macro {
2    ($($x:expr),*) => {
3        {
4            $(
5                dbg!($x);  // 在宏内部调试
6            )*
7        }
8    };
9}
10

3. 编译时打印

1// 在过程宏中打印调试信息
2eprintln!("Processing: {:?}", tokens);
3

实战综合案例:创建一个状态机宏

这个案例综合运用了多种宏技术:

1// 定义状态机语法
2macro_rules! state_machine {
3    (
4        $name:ident {
5            states: [ $($state:ident),* $(,)? ]
6            transitions: {
7                $($from:ident => $to:ident on $event:ident),* $(,)?
8            }
9        }
10    ) => {
11        // 定义状态枚举
12        #[derive(Debug, Clone, Copy, PartialEq)]
13        enum $name {
14            $($state),*
15        }
16        
17        // 定义事件枚举
18        paste::paste! {
19            #[derive(Debug)]
20            enum [<$name Event>] {
21                $($event),*
22            }
23        }
24        
25        // 实现状态转换
26        impl $name {
27            fn transition(&self, event: &paste::paste!{[<$name Event>]}) -> Option<Self> {
28                match (self, event) {
29                    $(
30                        (Self::$from, paste::paste!{[<$name Event>]::$event}) => {
31                            Some(Self::$to)
32                        }
33                    )*
34                    _ => None,
35                }
36            }
37        }
38    };
39}
40
41// 使用状态机宏
42state_machine! {
43    TrafficLight {
44        states: [Red, Yellow, Green]
45        transitions: {
46            Red => Green on TimerExpired,
47            Green => Yellow on TimerExpired,
48            Yellow => Red on TimerExpired,
49        }
50    }
51}
52
53fn main() {
54    let mut light = TrafficLight::Red;
55    println!("初始状态: {:?}", light);
56    
57    light = light.transition(&TrafficLightEvent::TimerExpired).unwrap();
58    println!("转换后: {:?}", light);  // Green
59}
60

总结

宏是 Rust 的超能力,但也需要谨慎使用。记住这些要点:

✅ 使用宏的好时机

  • 消除大量重复代码
  • 创建 DSL 让代码更简洁
  • 在编译时验证或生成代码
  • 实现无法用泛型表达的抽象

❌ 避免使用宏的情况

  • 简单的值计算(用函数)
  • 普通的类型抽象(用泛型)
  • 会让代码难以理解的地方

🎯 学习路径建议

  1. 初学者:从 macro_rules! 开始,理解模式匹配
  2. 进阶者:学习派生宏,理解 synquote
  3. 高级开发者:掌握属性宏和函数式宏,创建 DSL

📚 推荐资源


最后的忠告: 宏就像辣椒酱,适量使用能让你的代码美味可口,但过量使用会让人难以下咽。在写宏之前,问问自己:"这个真的需要宏吗?" 如果答案是肯定的,那就大胆地挥舞你的元编程魔法棒吧!🪄✨

Happy macro coding, Rustaceans! 🦀


Rust宏编程完全指南:用元编程解锁Rust的终极力量》 是转载文章,点击查看原文


相关推荐


【毕设】前后端(无模型训练)
2301_815389372026/3/8

后端 第一步,先建一个项目文件夹。 打开你电脑上任意一个地方,新建一个文件夹,就叫 ebike-detection,然后把你的 best.pt 复制进去。 第二步,安装Flask和相关依赖。 打开命令提示符(按 Win+R,输入 cmd,回车),然后把下面这行命令复制进去运行: pip install flask flask-cors ultralytics pillow 好,第三步,创建Flask后端文件。 在你的 ebike-detection


Node.js 安装与配置完全指南:从零开始搭建开发环境
张3蜂2026/2/28

目录 引言 第一部分:Node.js 简介与版本选择 1.1 什么是 Node.js? 1.2 Node.js 版本介绍 第二部分:Node.js 安装方式详解 2.1 方式一:官方安装包(最简单,适合初学者) Windows/macOS 安装步骤: 2.2 方式二:包管理器安装(适合 Linux 用户) Ubuntu/Debian 系统安装步骤: CentOS/RHEL 系统安装步骤: macOS 使用 Homebrew 安装: 2.3 方式三:使用 NVM 安装(最推


ThreadForge v1.1.0 发布:让 Java 并发更接近 Go 的开发体验
一只叫煤球的猫2026/2/20

正好春节放假,自驾去了陕西、河南、安徽,一路上走走停停。 白天基本在路上,晚上在酒店或者服务区休息时,抽一些时间继续打磨 ThreadForge。 一点点补了个 v1.1.0 出来。 仍然保持 ThreadForge 的目标: 让 Java 能写出更简单、更可推理、更可观测的并发代码。 这次版本,重点补齐了并发开发里几个还算常见的能力。 v1.1.0 核心更新 Retry Policy(失败重试) 支持 scope 级默认重试,也支持任务级覆盖,不再到处手写 while/try-catch


git pull拉取的时候碰到报错:error: 您对下列文件的本地修改将被合并操作覆盖 请在合并前提交或贮藏您的修改。
skywalk81632026/2/11

git pull拉取的时候碰到报错: error: 您对下列文件的本地修改将被合并操作覆盖:         data/processed/acnes_related_data.csv         data/processed/activity_data.csv         data/processed/hemolytic_data.csv         data/raw/active_peptides.csv         data/raw/hemolytic.csv      


自己搭邮件服务器有多难?我用 Mailu 跑通了整套企业邮箱
GetcharZp2026/2/3

从“为什么要自建邮箱”讲起,拆解 Mailu 的架构、优缺点,以及普通人也能照着做的安装实战。 Github:github.com/Mailu/Mailu 官网:mailu.io/ 这几年,越来越多团队开始重新审视一件事:邮箱,到底要不要掌握在自己手里? 用第三方企业邮箱当然省事,但账号封禁、功能限制、隐私不可控的问题,一旦遇到,几乎没有回旋余地。于是,自建邮件服务器这件事,又被不少技术团队重新捡了起来。 而在一堆方案里,Mailu 是被频繁提到的一个名字。 Mailu 是什么?一句话先讲明


VS code 类产物中 win11 终端字体内容和颜色 加粗不匹配问题
小兵张健2026/1/24

我尝试了各种方式,换字体,改配置,结果还是乱的,有人知道怎么搞吗?跪求,Mac好像天然就没问题,急急急大佬们


Django 踩坑记:OceanBase 4012 Timeout 两条红线,语句超时 vs 事务超时一次讲透
哈里谢顿2026/1/16

一、4012 是谁抛的? Django 本身没有 4012 错误码,它是 OceanBase 的“杀手”信号: 当前 SQL 或 当前事务累计执行时间 ≥ 系统阈值,直接返回 4012。 二、两条红线长啥样? 变量名默认阈值计时对象触发后果ob_query_timeout10 000 000 µs = 10 s单条 SQL 执行时长这条语句被杀,事务可继续ob_trx_timeout100 000 000 µs = 100 s事务 begin→


HarmonyOS一杯冰美式的时间 -- @Env
猫猫头啊2026/1/8

一、前言 该系列依旧会带着大家,了解,开阔一些不怎么热门的API,也可能是偷偷被更新的API,也可以是好玩的,藏在官方文档的边边角角~当然也会有一些API,之前是我们辛辛苦苦的手撸代码,现在有一个API能帮我们快速实现的,希望大家能找宝藏。 如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏 二、@Env的诞生背景 OK,步入正题把,在多设备开发的场景中,我们经常需要根据不同的设备环境(比如窗口大小、横竖屏等)来调整UI布局。以前我


Nop入门:使用Excel模板生成包含图表的报表
canonical_entropy2025/12/30

讲解视频:Nop入门:使用NopReport导出Excel图表_哔哩哔哩_bilibili Nop平台内置了一个非常精简的中国式报表引擎NopReport,它可以实现商业报表引擎如FineReport和润乾报表的核心功能。NopReport很巧妙的利用Excel单元格的批注机制来存放扩展信息,可以直接使用Excel模板来作为报表模板,这样大大简化了报表制作过程,并且可以复用客户已有的各种业务表格。 在实现层面,NopReport没有使用Apache POI库,而是选择了直接使用流式接口解析Off


什么?还不知道git cherry pick?
少年姜太公2025/12/21

前言 上周四在公司开发代码时,一下没注意从测试分支dev上拉了代码并进行开发,最后要上线的时候才猛然发现分支拉错了,一看gitLab上改了几十个文件,再一查发现自己拉代码拉错了😭😭😭,本来都准备下班了,结果硬是新建了一个分支,把四五个文件近1000行代码一点一点cv到新分支上,结果搞完一查才发现git有个cherry pick的命令可以直接搞完,希望jym看完这篇文章以后不要像我一样做这种一点一点cv的蠢事了😭😭😭 什么是cherry pick cherry pick顾名思义,就是樱

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客