Rust的macro小记(一)
written on Thu 05 September 2019 by importcjj
这篇记录一下Rust中的Function-like macros,也就是形如println!(...)形式的宏。
先来一个简单的
macro_rules! hello {
() => {
println!("Hello, World!");
}
}
fn main() {
hello!();
}
上面这个简单的例子中, 定义了一个叫做hello的宏,匹配规则为空。作用就是在使用该宏的地方,插入代码println!("Hello, World!");
。
接下来我们加入匹配规则,来让这个宏更加的灵活.
macro_rules! hello {
() => {
println!("Hello, World!");
};
($name:expr) => {
println!("Hello, {}!", $name);
}
}
fn main() {
hello!();
hello!("rust");
}
现在增加了一个匹配规则,这样编译器在在工作时会按规则匹配,使用不同的代码分支。那可不可以让hello
支持多个参数呢?
当然可以!宏的匹配规则支持重复模式!
*
模式重复零次或多次+
模式重复一次或多次?
模式出现零次或一次
让我们来稍加改造一下:
macro_rules! hello {
() => {
println!("Hello, World!");
};
($($name:expr),*) => {
$(
println!("Hello, {}!", $name);
)*
}
}
fn main() {
hello!();
hello!("rust");
hello!("rust", "macro");
}
这下我们可以让hello
接收多个参数了, 每个参数都会被展开成一句print!
代码。如果你观察仔细的话,会发现我们在定义模式的时候有一个逗号,但是使用的参数macro
后面没有逗号,但是规则却能正常匹配,超级方便有木有!
到目前为止,我们使用的方式还是停留在函数调用的形式,接下来来一个复杂一点的例子。我相信很多人都接触过rpc,那么接下来就来定义我们自己的rpc client。
首先我们确定我们想要的形式
pub struct Ping {}
pub struct Pong {}
service! {
service demo {
rpc ping(Ping) returns (Pong);
}
}
fn main() {
use self::demo::{Client};
let c = Client::new();
let rsp = c.ping(Ping{});
assert!(rsp, Pong{});
}
好,我们已经知道想要的形式了,接下来就让我们实现我们的service
宏吧!
macro_rules! service {
(
service $srv_name:ident {
$(rpc $method_name:ident($input:tt) returns ($output:tt);)*
}
) => {
}
}
这部分我们定义了宏的rule
, 可以看到是对应我们之前提出的使用形式的。而且,再规则的内层使用重复的形式允许我们定义多个rpc方法。在这里给出参数的含义
- $srv_name: service的名称
- $method_name: rpc方法的名称
- $input: rpc方法的输入参数
- $output: rpc方法的输出结果
然后,我们来定义宏的输出部分
macro_rules! service {
(
service $srv_name:ident {
$(rpc $method_name:ident($input:tt) returns ($output:tt);)*
}
) => {
pub mod $srv_name {
$(use super::$input;)*
$(use super::$output;)*
pub struct Client{
}
impl Client {
pub fn new() -> Self {
Client {}
}
$(
pub fn $method_name(&self, _input: $input) -> $output {
}
)*
}
}
}
}
到了这里,基本完成了输出部分的主体代码。现在遇到了一个问题,就是如何实现结果的返回呢。在实际使用中,client会接收的远程服务返回的字节数据,然后使用特定的protocol来完成结果的解码,所以我们的输入输出类型均要实现解码编码的功能,由于例子的ping方法简单,所以只在这里为方法的返回类型pong实现一个简易的解码函数.
pub trait Message {
fn decode() -> Self;
}
pub struct Pong {
}
impl Message for Pong {
fn decode() -> Self {
Pong{}
}
}
我们定义了一个trait Message
, 目前只有一个decode方法。这里的decode方法简化了参数, 正常情况下会输入字节数据进行反序列化。然后,我们又为Pong
实现了Message
。接下来就是使用它们的时候了。
macro_rules! service {
(
service $srv_name:ident {
$(rpc $method_name:ident($input:tt) returns ($output:tt);)*
}
) => {
pub mod $srv_name {
// 引入Message
use super::Message;
$(use super::$input;)*
$(use super::$output;)*
pub struct Client{
}
impl Client {
pub fn new() -> Self {
Client {}
}
$(
pub fn $method_name(&self, _input: $input) -> $output {
// TODO call the remote server
$output::decode();
}
)*
}
}
}
}
友情提示,可以使用cargo的小工具expand
展开宏代码,得到
pub mod demo {
use super::Message;
use super::Ping;
use super::Pong;
pub struct Client {}
impl Client {
pub fn new() -> Self {
Client {}
}
pub fn ping(&self, _input: Ping) -> Pong {
Pong::decode()
}
}
}
终于顺利完成啦!