18 - 宏
-
assert_eq!宏:可以生成包含断言文件名和行号的错误消息。 -
宏:是扩展语言的一种方式。在编译期间,在检查类型和生成任何机器码之前,每个宏调用都会被扩展(expanded)。
assert_eq!(gcd(6, 10), 2); // 上述宏扩展后的代码 match (&gcd(6, 10), &2) { (left_val, right_val) => { if !(*left_val == *right_val) { panic!("assertion failed: '(left == right)', \ (left: '{:?}', rigth: '{:?}')", left_val, right_val); } } } -
Rust 宏可以与语言的其他组件整合,不容易出错。
- 宏调用始终都会以一个感叹号来标记,不容易被忽视。
- Rust 宏永远不会插入不匹配的方括号或圆括号。
- Rust 宏自带模式匹配,便于维护和扩展。
18.1 - 宏基础
-
macro_rules!是 Rust 中定义宏的主要方式。marcro_rule! assert_eq { ($left: expr, $right: expr) => ( // 模式 { // 模板 match (&$left, &$right) { if !(*left_val == *right_val) { panic!("assertion failed: '(left == right)' \ (left: '{:?}', right: '{:?}')", left_val, right_val) } } } ); }- 注意上述代码中
assert_eq后面没有感叹号。 - 只有调用宏的时候才需要包含感叹号
!,定义的时候则不需要。
- 注意上述代码中
-
file!、line!和macro_rules!,本身是内置在编译器中的。 -
宏定义定义的另一种方式:过程宏(procedural macro)。
-
macro_rule!定义的宏完全基于模式匹配实现逻辑。( 模式1 ) => ( 模板1 ); ( 模式2 ) => ( 模板2 ); ...-
模式和模板周围可以不用圆括号,而使用方括号或花括号。
-
以下形式都是等价的:
assert_eq!(gcd(6, 10), 2); assert_eq![gcd(6, 10), 2]; assert_eq!{gcd(6, 10), 2} // 在使用花括号时,最后的分号是可选的。
-
-
括号的使用惯例或约定:
- 在调用
assert_eq!时,使用圆括号 - 在调用
vec!时使用方括号 - 在调用
macro_rules!时使用花括号
- 在调用
18.1.1 - 宏扩展基础
-
不能在定义宏之前调用宏,Rust 对宏调用是边分析边扩展的。
-
宏模式操作的是记号(token),比如数字、名称、标点等 Rust 程序的语法符号。注释和空白不是记号。
-
对于宏模式中的
$left:expr的含义:
expr是表达式,其值会赋值给$left。- 宏模式与正则表达式一样,只有少数特殊字符会触发特殊的匹配行为。
- 其他字符,如逗号,需要按字面匹配,否则匹配会失败。
18.1.3 - 重复
-
标准的
vec!宏有两种形式:// 重复一个值N次 let buffer = vec![0_u8; 1000]; // 一个由逗号分隔的值列表 let numbers = vec!["udon", "ramen", "soba"]; -
vec!宏的实现:macro_rules! vec { ($elem: expr; $n: expr) => { ::std::vec::from_elem($elem, $n) }; ( $( $x:expr ),* ) => { <[_]>::into_vec(Box::new([ $( $x ),* ])) }; ( $( $x:expr ),+ ,) => { vec![ $( $x ),* ] }; } -
重复的模式:
模式 含义 $( ... )*匹配 0 或多次,没有分隔符 $( ... ),*匹配 0 或多次,以逗号分隔 $( ... );*匹配 0 或多次,以分号分隔 $( ... )+匹配 1 或多次,没有分隔符 $( ... ),+匹配 1 或多次,以逗号分隔 $( ... );+匹配 1 或多次,以分号分隔 $x是一组表达式;<[_]>表示某种类型值的切片。,表示匹配带有额外逗号的列表。
18.2 - 内置宏
-
file!():扩展为一个字符串字面量,即当前文件名。 -
line!():扩展为一个u32字面量,代表当前行(从 1 开始) -
column!():扩展为一个u32字面量,代表当前列(从 0 开始) -
stringify!(...tokens...):扩展为一个字符串字面量,包含给定的记号。assert!使用此内置宏生成包含断言代码的错误消息。- 如果参数包含一个宏,那么不会发生扩展,如
stringify(line!()),任意参数为一个字符串"line!()"。
-
concat!(str0, str1, ...):扩展为一个字符串字面量,是拼接其参数之后的结果。 -
cfg!(...):扩展为一个布尔值常量,如果当前构建配置与括号中的条件匹配,则为true。 -
env!("VAR_NAME"):扩展为一个字符串,即指定环境变量在编译时的值。如果指定的变量不存在,会发生编译错误。常与Cargo结合使用。 -
option_env!("VAR_NAME"):与env!相同,不过返回Option<&'static str>,如果指定变量没有设置,则返回None。 -
include!("file.rs"):扩展为指定文件的内容,必须是有效的 Rust 代码,比如表达式或特性项的序列。 -
include_str!("file.txt"):扩展为一个&'static str,包含指定文件的文本。-
使用方法:
const COMPOSITOR_SHADER: &str = include_str!("../resources/compositor.glsl"); -
如果指定的文件不存在,或者文本不是有效 UTF-8,会导致编译错误。
-
-
include_bytes!("file.dat"):将文件作为二进制数据进行扩展。其结果是&'static [u8]类型的值。 -
内置宏的规则:
- 在编译时被处理,如果文件不存在或不能读,那编译就会失败。
- 所有宏不能在运行时失败。
- 在任何情况下,如果文件名是相对路径,就会相对于包含当前文件的目录来解析。
18.3 - 调试宏
-
宏扩展的过程是不可见的。
- Rust 在扩展宏的过程中,在发现某些错误时会打印出一条错误消息。
- 但不会显示包含错误的完全扩展后的代码。
-
rustc可以展示代码在扩展所有宏之后的信息。
cargo build --verbose可以看到Cargo时如何调用rustc。- 即复制
rustc命令,再添加-Z unstable-options --pretty expanded选项。
-
log_syntax!()宏:在编译时,可以把其参数打印到终端。
- 可以用于实现类似
println!的调试。 - 要求带有
#![feature(log_syntax)]特性标志。
- 可以用于实现类似
-
让 Rust 编译器把所有宏调用的日志打印到终端上。
- 在代码某个地方插入
trace_macros!(true);。 - 这样,Rust 每扩展一个宏,都会打印宏的名字和参数。
- 在代码某个地方插入
18.4 - 自定义一个宏 ——json! 宏
开发一个 json! 宏,接收一个 JSON 值作为参数,然后为其扩展为类似下述 Rust 表达式:
let students = json!([
{
"name": "Jim Blandy"
"class_of": 1926
"major": "Tibetan throat singing"
},
]);
18.4.1 - 片段类型
-
macro_rules!宏支持的片段类型:片段类型 匹配(示例) 后面可以加… expr表达式: 2 + 2, "udon", x.len()=> , ;stmt表达式或声明,不包含末尾的分号(优先使用 expr或block)=> , ;ty类型: String、Vec<u8>、(&str, bool)=> , ; =path路径: ferns、::std::sync::mpsc=> , ; =pat模式: _、Some(ref x)=> , =item特性项: struct Point {x: f64, y: f64}、mod ferns;不限 block代码块: s += "ok\n"; true不限 meta属性体: inline、derive(Copy, Clone)、doc="3D models."不限 ident标识符: std、Json、longish_variable_name不限 tt记号树: ;、>=、{}、[0 1 (+ 0 1)]不限 -
json!宏的定义如下:macro_rules! json { (null) => { Json::Null }; ([ $( $element:tt ),* ]) => { Json::Array(...) }; ({ $( $key:tt : $value:tt ),* }) => { Json::Object(...) }; ($other:tt) => { ... // TODO: 返回Number、String或Boolean }; }
18.6 - 超越 macro_rules!
- 过程宏:
- 支持扩展
#[derive]属性,以处理自定义特型。 - 作为 Rust 函数实现,并非一个声明性的规则集。
- 支持扩展
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第二十章
原文地址
本文深入探讨了Rust语言中的宏特性,包括assert_eq!宏的使用、宏的基础知识、内置宏的功能、调试宏的应用以及自定义json!宏的实现过程。通过具体的例子,读者能够了解到宏如何帮助提高代码质量与效率。
73

被折叠的 条评论
为什么被折叠?



