当前位置:网站首页>【Rust 笔记】18-宏
【Rust 笔记】18-宏
2022-07-06 03:17:00 【phial03】
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 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第二十章
原文地址
边栏推荐
- February 14, 2022 Daily: Google long article summarizes the experience of building four generations of TPU
- Lua uses require to load the shared library successfully, but the return is Boolean (always true)
- 2022工作中遇到的问题四
- Pelosi: Congress will soon have legislation against members' stock speculation
- OCR文字识别方法综述
- 八道超经典指针面试题(三千字详解)
- Redis SDS principle
- Daily question brushing plan-2-13 fingertip life
- 电机控制反Park变换和反Clarke变换公式推导
- 记录一下逆向任务管理器的过程
猜你喜欢
随机推荐
真机无法访问虚拟机的靶场,真机无法ping通虚拟机
Era5 reanalysis data download strategy
Research on cooperative control of industrial robots
StrError & PERROR use yyds dry inventory
mysqldump数据备份
Arabellacpc 2019 (supplementary question)
EDCircles: A real-time circle detector with a false detection control 翻译
. Net 6 and Net core learning notes: Important issues of net core
IPv6 jobs
[concept] Web basic concept cognition
SAP ALV颜色代码对应颜色(整理)
Restful style
What is the investment value of iFLYTEK, which does not make money?
C # create self host webservice
多态day02
3857墨卡托坐标系转换为4326 (WGS84)经纬度坐标
Erreur de la carte SD "erreur - 110 whilst initialisation de la carte SD
这些不太会
Summary of Bible story reading
Item 10: Prefer scoped enums to unscoped enums.