Rust 宏笔记

Rust 宏笔记
https://ld246.com/article/1592390738395
这篇文章说的是?

Rust 的宏。

宏按照来源分类:

声明宏(Declarative Macro)和过程宏(Procedural Macro)。前者指的是用某种语法直接声明出的宏。后者是对应直接生成抽象语法树的过程的宏。

直觉上过程宏更隐式,更全能;声明宏更可读,更直接。

如何定义声明宏?

现在用 macro_rules!。以后可能还有别的办法。

如何定义过程宏?

以后再说。

宏按照使用方式分类:

属性宏:给声明添加属性的宏,例如 #[derive(Debug)] 和 #[test]。

调用宏:像函数一样的宏,例如 println!。

来源分类和使用方式分类之间的关系如何?

目前的声明宏都是用 macro_rules! 声明出的,它声明出的一定是调用宏。过程宏可以产生属性宏,也可以产生调用宏。

也就是说,属性宏都是过程宏,调用宏可能是声明宏或者过程宏。

println! 宏大概是什么样子?

macro_rules! println {
  () => (println!("\n"));
  ($fmt: expr) => (print!(concat!($fmt, "\n")));
  ($fmt: expr, $($(arg:tt)*) =>
    (print!(concat!($fmt, "\n"), $($arg)*));
}

这个宏有几部分?

有三个部分,输入分别是 ()、( f m t : e x p r ) 和 ( fmt:expr) 和 ( fmt:expr)(fmt: expr, ( ( (args:tt)*),依次扩展成 => 后,圆括号内部的部分。每个部分是一条规则,每条规则以 ; 结尾。

=> 后的圆括号是必须的吗?

不能省略,但是可以换成 {} 或者 []。

$fmt: expr 是什么?

$fmt 是对宏参数的捕获,类似于函数的参数。expr 表示这个捕获的类型是表达式,也就是会它会生成具体的值。具体到此处,它代表生成 println! 的格式字符串的表达式。

捕获有什么用?

宏的替换结果里可以用 f m t 代 表 要 替 换 这 个 捕 获 。 例 如 p r i n t l n ! ( " H e l l o " ) 中 , fmt 代表要替换这个捕获。例如 println!("Hello") 中, fmtprintln!("Hello")fmt: expr 捕获了 “Hello”,所以 print!(concat!($fmt, "\n)) 中的 $fmt 会被替换为 “Hello”,所以展开成 print!(concat!(“Hello”, “\n”))。

展开的宏中如果还有宏,还会继续展开吗?

会,上面 println! 展开之后的内容中有 print! 和 concat!,它们都会再次展开。这是理所当然的行为,这个问题只是为了让 Rust 的宏跟 C++ 的宏划清界限。

都有什么捕获类型?

类型 	意义
item 	语言项,模块、定义、声明等
block 	代码块,花括号限定的代码
stmt 	语句,分号结尾的代码
expr 	表达式,会生成具体的值
pat 	模式
ty 	类型
ident 	标识符
path 	路径,指从 crate 到 mod 的定位
meta 	元信息
tt 	TokenTree 的缩写,词条树
vis 	可见性,例如 pub
lifetime 	生命周期参数

( ( (arg:tt)* 是什么意思?

单独看 $arg:tt 表示匹配一个词条树的捕获,在外面套上 $()* 表示匹配若干次词条树。

( ( (arg)* 是什么意思?

单看 $arg,表示在宏里替换捕获 $arg,外面套上 $()* 表示使用所有匹配的捕获。这个用法跟它的捕获语法是对应的。

( ( (arg:tt)* 能匹配什么?

println! 在第一个参数之后的所有东西。这个宏不止可以传递像函数一样的参数,还可以像 Python 那样传递命名参数,例如:

println!(“Hello, {name}”, name=“Luna”);

这样的参数 ( ( (arg:tt)* 也能捕获到。

如果想只捕获(不定个数个)函数参数应该如何做?

( ( (arg: expr),* 或者 ( ( (arg: expr,)*。

这两个有什么不同?

后者也匹配逗号结尾的参数列表。

Rust 的函数参数列表最后可以添加逗号,也可以不加。如果想让宏表现的尽量接近函数,应该两种情况都处理。

举个例子。

macro_rules! hash_map {
  ($($key:expr => $value:expr),*) => {{
    let mut map = ::std::collections::HashMap::new();
    $( map.insert($key, $value); )*
    map
  }};
  ($($key:expr => $value:expr),*) =>
    (hash_map! ($($key => $value),*));
}

怎么使用?

let map = hash_map! (1 => "one", 2 => "two", 3 => "three");

如何获取宏可变参数的长度?

没有直接的办法,但可以想些技巧。

macro_rules! unit {
  ($($x:tt)*) => (());
}

macro_rules! count {
  ($($x:expr),*) => (<[()]>::len(&[$(unit!($x)),*]));
}

它是如何工作的?

unit! 接受任意什么东西,返回一个 unit(())。count! 宏把参数填给 unit!,构造了一个 unit 数组,数组长度就是参数的个数。

好处是,unit 不占空间,unit 的数组也是。

count! 宏需要 unit! 宏才能工作,但 unit! 宏本身没什么用,能不能把 unit! 变成私有的?

也没有直接的办法。基本上的技巧是换成一个比较难直接用到的规则:

macro_rules! count {
  (@unit $($x:tt)*) => (());

  ($($x:expr),*) => (<[()]>::len(&[$(count!(@unit $x)),*]));
}

要是有人非要写 count!(@unit …),也没法阻止。但那个奇奇怪怪的 @ 已经暗示了这是一个内部实现,这就足够了。

#[macro_export] 有什么作用?

标记一个宏可以在其他包中使用。也就是说,默认情况下,宏不能在定义的包外使用。

#[macro_use] 有什么用?

在 Rust 2015 中,在外部包的声明语前,用它标记要导入另一个包的宏。另外,它还标记一个 mod 的宏可以在外面使用。

宏重名会怎样?

后导入的会覆盖先前导入的,不会发生错误。

$crate 有什么用?

它看起来像一个捕获,但不是在宏参数列表里捕获的。它会扩展成当前包的名字。需要这个捕获的原因是,当前包无法决定此包被其他包导入的时候,使用的是什么名字。所以如果要使用当前包的函数或者别的东西,就需要从 $crate 开始写路径。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值