Rust的macro小记(一)

written on Thu 05 September 2019 by

这篇记录一下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()
        }
    }
}

终于顺利完成啦!

This entry was tagged on #rust

comments powered by Disqus
 

Tags