宏的扩充
宏是一种捷径,在类型检查之前,甚至在编译之前扩充成Rust代码。
调用宏的时候用圆括号或方括号或花括号都是一样的。如果用花括号后边的分号可以省略。习惯上assert,print这类的用圆括号,vec用方括号,macro_rules用花括号。
因为宏的扩充在编译开始之前,甚至在扫描其他代码之前,所以宏的定义必须在宏的调用之前。
匹配
宏的扩充很像是模式匹配。匹配的是token:数字,命名,符号等;注释和空白符除外。
这个匹配中,$left:expr告诉Rust去匹配一个表达式,并赋给$left。想正则一样只有一些符号能匹配一些特殊的情况,除此之外想逗号只能简单逐字匹配。
在上面的例子里匹配到的表达式被是$left和$right然后被变量left_val和right_val取代,应为匹配的表达式会在扩充时替换成匹配到的表达式,left_val和right_val是表达式返回的值,如果不替换成值而用
if !($left == $right) {
panic!("assertion failed: `(left == right)` \
(left: `{:?}`, right: `{:?}`)", $left, $right)
}
那第三行的$left, $right还会被替换成原表达式,那这个表达式就会运行两次。
宏里如果不使用引用也会获取值的所有权。
重复
macro_rules! vec {
($elem:expr ; $n:expr) => {
::std::vec::from_elem($elem, $n)
};
( $( $x:expr ),* ) => {
<[_]>::into_vec(Box::new([ $( $x ),* ]))
};
( $( $x:expr ),+ ,) => {
vec![ $( $x ),* ]
};
}
其中第二个匹配$( $x:expr ),*
,就像正则一样*
表示0或多个重复,但,
不是匹配的对象而是表示重复是用,
分割的,而且如果末尾有多余的,
还匹配不到。里面的$x:expr
表示每个重复的表达式。有多种重复的形式的匹配:
Pattern | Meaning |
---|---|
$( … )* | Match 0 or more times with no separator |
$( … ),* | Match 0 or more times, separated by commas |
$( … );* | Match 0 or more times, separated by semicolons |
$( … )+ | Match 1 or more times with no separator |
$( … ),+ | Match 1 or more times, separated by commas |
$( … );+ | Match 1 or more times, separated by semicolons |
$( … )? | Match 0 or 1 times with no separator |
$( … ),? | Match 0 or 1 times, separated by commas |
$( … );? | Match 0 or 1 times, separated by semicolons |
除了用作匹配传入的表达式,还可以用来生成重复的表达式,<[_]>::into_vec(Box::new([ $( $x ),* ]))
表示匹配到的$x以逗号分割重复。
这里intovec是数组的方法,这行代码是首先创建一个数组,用box把这个数组放到堆,然后用数组的into_vec方法把这个数组转成向量,`[]中的
_`是类型推断。
内建的宏
file!(), line!(), column!()
返回文件路径,当前行,列。如果被多个宏层层调用,返回的是最顶层的那个宏的位置。column返回的是c
所在的列。
stringify!(…tokens…)
返回token本身的字符串,assert用这个来返回错误的代码。而且它里面调用别的宏不会被扩展。
concat!(str0, str1, …)
cfg!(…)
env!(“VAR_NAME”) , option_env!(“VAR_NAME”)
include!(“file.rs”)
include_str!(“file.txt”)
include_bytes!(“file.dat”)
todo!(), unimplemented!()
matches!(value, pattern)
判断value是不是匹配pattern。
宏的debug
但这几个功能只有nightly能用。
使用cargo build —verbose可以查看rustc编译的命令。在编译命令里传入-Z unstable-options —pretty expanded可以查看扩展后的代码。
打开#![feature(log_syntax)],使用log_syntax!() 打印宏。
打开#![feature(trace_macros)],trace_macros!(true) 来打印后面的宏。
实现
expr是用来匹配rust表达式的片段,还有别的片段类型。
Fragment type | Matches (with examples) | Can be followed by… |
---|---|---|
expr | An expression: 2 + 2, “udon”, x.len() |
=> , ; |
stmt | An expression or declaration, not including any trailing semicolon (hard to use; try expr or block instead) |
=> , ; |
ty | A type: String, Vec |
=> , ; = | { [ : > as where |
path | A path (discussed): ferns,::std::sync::mpsc |
=> , ; = | { [ : > as where |
pat | A pattern (discussed): _,Some(ref x) |
=> , = | if in |
item | An item (discussed): struct Point { x: f64, y: f64 },mod ferns; |
Anything |
block | A block (discussed): { s += “ok\n”; true } |
Anything |
meta | The body of an attribute (discussed): inline,derive(Copy, Clone),doc=”3D models.” |
Anything |
ident | An identifier: std, Json, longish_variable_name |
Anything |
literal | A literal value: 1024, “Hello, world!”, 1_000_000f64 |
Anything |
lifetime | A lifetime: ‘a, ‘item, ‘static |
Anything |
vis | A visibility specifier: pub, pub(crate), pub(in module::submodule) |
Anything |
tt | A token tree (see text): ;, >=, {}, [0 1 (+ 0 1)] |
Anything |
第三列的可以在后面的字符是按Rust语法规定的。比如expr后面只可能出现那几个字符。
宏可以像函数一个递归调用自己,默认的递归极限是64,可以用#![recursion_limit = “256”] 修改。
如果在宏里声明了变量,有可能会和外面的变量名冲突,这样扩展成代码的时候似乎会有问题,但Rust有hygiene机制。但这样也会有问题:
macro_rules! setup_req {
() => {
let req = ServerRequest::new(server_socket.session());
}
}
fn handle_http_request(server_socket: &ServerSocket) {
setup_req!(); // declares `req`, uses `server_socket`
... // code that uses `req`
}
如果想要用宏来做一个本地变量的复制是不能直接用的,因为hygiene会防止本地变量的冲突。可以把两个变量传进宏进行操作
macro_rules! setup_req {
($req:ident, $server_socket:ident) => {
let $req = ServerRequest::new($server_socket.session());
}
}
fn handle_http_request(server_socket: &ServerSocket) {
setup_req!(req, server_socket);
... // code that uses `req`
}
引用
使用#[macro_use] mod json;
引用别的模块定义的宏。引用之后,子模块也可以使用这些宏。或者在宏的定义上加上#[macro_export] 。
如果宏里用到的模块在宏调用的地方没有引入,宏是不能运行的。因此实际上export的宏是不能有本地依赖的。所以宏里依赖的项都要使用绝对路径。$crate是宏里的一个特殊片段,用来表示项目的跟模块。
避免语法错误
macro_rules! complain {
($msg:expr) => {
println!("Complaint filed: {}", $msg);
};
(user : $userid:tt , $msg:expr) => {
println!("Complaint from user {}: {}", $userid, $msg);
};
}
complain!(user: "jimb", "the AI lab's chatbots keep picking on me");
这个是会报错的,因为在第一个匹配里,rust会把输入的参数当做一个语法错误。虽然他是复合第二个匹配的。但在宏里遇到语法错误是不算匹配失败的,而是fatal error。
一般的解决办法是:
将每个规则都设置的规范
macro_rules! complain {
(msg : $msg:expr) => {
println!("Complaint filed: {}", $msg);
};
(user : $userid:tt , msg : $msg:expr) => {
println!("Complaint from user {}: {}", $userid, $msg);
};
}
将更具体的规则放在前面。