暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

写给 Web 开发者的 Rust 语言入门教程(17.普通宏)

最后的叶子的奇妙小屋 2021-07-27
917

之前的文章:

0.引言

1.模块基础

2.变量

3.字符串

4.分支循环

5.数据表示

6.模块化

7.接口抽象

8.泛型

9.错误处理

10.内存布局

11.所有权

12.引用传递

13.引用字段

14.引用计数

15.泛化特化

16.闭包

本章主要介绍 rust 宏的基本用法,主要包括 macro_rules 的基本语法规则和各种细节语法规则。

宏的基础语法

宏是 rust 中基本的代码抽象机制,可以有效减少代码中重复的代码片段。rust 的宏分为两种:使用 macro_rules! 定义的普通宏( macro )和用法非常复杂的过程宏( procedural macro )。本章介绍较为易用的前者。

在编写代码时,有时会写出一些重复或近似重复的代码片段,这些代码片段有时还很难使用函数、 trait 或其他语法来进行抽象,此时可以考虑使用宏。例如:

    struct WholeDayTemperature {
    morning: u32,
    noon: u32,
    evening: u32,
    midnight: u32,
    }


    impl WholeDayTemperature {
    fn print_all(&self) {
    println!("Temperature for morning is {}", self.morning);
    println!("Temperature for noon is {}", self.noon);
    println!("Temperature for evening is {}", self.evening);
    println!("Temperature for midnight is {}", self.midnight);
    }
    }


    fn main() {
    let wdt = WholeDayTemperature {
    morning: 30,
    noon: 36,
    evening: 34,
    midnight: 28,
    };
    wdt.print_all();
    }

    上面这段代码中,对四个字段的输出是相似的代码逻辑,但是无法使用函数来进行抽象。此时可以用宏来进行抽象,例如:

      struct WholeDayTemperature {
      morning: u32,
      noon: u32,
      evening: u32,
      midnight: u32,
      }


      impl WholeDayTemperature {
      fn print_all(&self) {
      // 定义一个宏,名字是 print_item
      macro_rules! print_item {
      // 这个宏接受一个参数,类型是变量名( ident )
      ($v: ident) => {
      // 将变量名转换成一个字符串,如 my_var 会变成 "my_var"
      let v_str = stringify!($v);
      println!("Temperature for {} is {}", v_str, self.$v);
      }
      }


      // 调用宏,传入 morning 作为参数
      print_item!(morning);
      // 这个调用等价于直接编写:
      // let v_str = stringify(morning);
      // println!("Temperature for {} is {}", v_str, self.morning);
      // 进一步等价于:
      // println!("Temperature for {} is {}", "morning", self.morning);


      // 以其他参数调用同一个宏
      print_item!(noon);
      print_item!(evening);
      print_item!(midnight);
      }
      }


      fn main() {
      let wdt = WholeDayTemperature {
      morning: 30,
      noon: 36,
      evening: 34,
      midnight: 28,
      };
      wdt.print_all();
      }

      元变量

      macro_rules! 调用中,可以指定接受的“元变量”及参数类型。元变量即 $ 开头的变量,与普通变量不同,它的值可以是一个变量名 ident 、表达式 expr 或其他“代码片段”。元变量支持的类型列表可以在 rust 文档中找到:

      https://doc.rust-lang.org/reference/macros-by-example.html#metavariables

      使用元变量时,会将对应的变量名(或表达式等)替换到这个元变量出现的位置上,如:

        macro_rules! print_item {
        ($v: ident, $desc: expr) => {
        println!("Temperature {} is {}", $desc, self.$v);
        }
        }
        print_item!(morning, "in the morning");
        // 等价于
        println!("Temperature {} is {}", "in the morning", self.morning);

        此外, macro_rules! 可以接受多种不同类型、不同数量的参数,用法类似于 match ,例如:

          macro_rules! print_item {
          // 如果传入两个参数,且参数分别为 ident 和 expr ,则匹配这个分支
          ($v: ident, $desc: expr) => {
          println!("Temperature {} is {}", $desc, self.$v);
          };
          // 如果只传入一个参数,且类型是 ident ,则匹配这个分支
          ($v: ident) => {
          let v_str = stringify!($v);
          println!("Temperature for {} is {}", v_str, self.$v);
          };
          }


          print_item!(morning, "in the morning");
          // 等价于
          println!("Temperature {} is {}", "in the morning", self.morning);


          print_item!(morning);
          // 等价于
          let v_str = stringify!(morning);
          println!("Temperature for {} is {}", v_str, self.morning);

          列表

          macro_rules! 也支持传入无限个参数,将参数表达为一个列表,用法例如:

            struct WholeDayTemperature {
            morning: u32,
            noon: u32,
            evening: u32,
            midnight: u32,
            }


            impl WholeDayTemperature {
            fn print_all(&self) {
            macro_rules! print_items {
            // 接受一个无限的参数列表 v (列表用 , 分隔)
            ($($v: ident),*) => {
            // 展开列表,
            // 对于列表中的每一项重复下面括号中的内容
            $(
            let v_str = stringify!($v);
            println!("Temperature for {} is {}", v_str, self.$v);
            )*
            };
            }
            print_items!(morning, noon, evening, midnight);
            }
            }


            fn main() {
            let wdt = WholeDayTemperature {
            morning: 30,
            noon: 36,
            evening: 34,
            midnight: 28,
            };
            wdt.print_all();
            }

            利用列表特性可以将代码变得更加简洁。上面这段代码与下例等价:

              macro_rules! whole_day_temperature {
              ($($v: ident),*) => {


              struct WholeDayTemperature {
              $(
              $v: u32,
              )*
              }


              impl WholeDayTemperature {
              fn print_all(&self) {
              $(
              let v_str = stringify!($v);
              println!("Temperature for {} is {}", v_str, self.$v);
              )*
              }
              }


              };
              }


              whole_day_temperature!(morning, noon, evening, midnight);


              fn main() {
              let wdt = WholeDayTemperature {
              morning: 30,
              noon: 36,
              evening: 34,
              midnight: 28,
              };
              wdt.print_all();
              }

              此外,调用宏时,除了 !(...) 的形式,也可以用 ![...] 或者 !{...} 的形式,有时会更好看,例如:

                whole_day_temperature! { morning, noon, evening, midnight }

                宏的作用域

                宏可以在 macro_rules! 代码块之后的子模块中直接访问。

                  mod my_mod {
                  mod my_sub_mod_a {
                  // 编译错误!
                  // macro_rules 定义之前不能调用
                  my_macro!();
                  }


                  macro_rules! my_macro {
                  () => { /* ... */ }
                  }


                  mod my_sub_mod_b {
                  // 子模块中可以直接调用
                  my_macro!();
                  }
                  }

                  如果需要在父模块或者其他 crate 中使用这个宏,需要加上 #[macro_export]

                    mod my_mod {
                    #[macro_export]
                    macro_rules! my_macro {
                    () => { /* ... */ }
                    }
                    }


                    // macro_export 使其可以在模块外调用
                    my_macro!();

                    在其他 crate 中使用这个 crate 定义的宏时,需要加上 #[macro_use] 。例如:

                      #[macro_use(my_macro)]
                      extern crate my_crate;

                      变量名拼接

                      需要注意的是, macro_rules! 中无法进行变量名的拼接。如果需要在宏中拼接字符串,可以使用 paste 这个第三方 crate 。首先在 Cargo.toml 中引入:

                        [dependencies]
                        paste = "1.0"

                        具体用法例如:

                          use paste::paste;


                          macro_rules! whole_day_temperature {
                          ($($v: ident),*) => {


                          struct WholeDayTemperature {
                          $(
                          $v: u32,
                          )*
                          }


                          impl WholeDayTemperature {
                          fn print_all(&self) {
                          $(
                          let v_str = stringify!($v);
                          println!("Temperature for {} is {}", v_str, self.$v);
                          )*
                          }


                          // paste! 内支持变量名拼接
                          paste! {
                          $(
                          // 拼接 set_ 和 $v ,生成一个 set_* 方法
                          fn [<set_ $v>](&mut self, t: u32) {
                          self.$v = t;
                          }
                          )*
                          }
                          }


                          };
                          }


                          whole_day_temperature!(morning, noon, evening, midnight);


                          fn main() {
                          let mut wdt = WholeDayTemperature {
                          morning: 30,
                          noon: 36,
                          evening: 34,
                          midnight: 28,
                          };
                          // 调用 set_* 方法
                          wdt.set_morning(26);
                          wdt.print_all();
                          }

                          macro_rules! 是一种简易的宏定义方式。过程宏比它更加强大,但用法也复杂得多。之后的文章将详细介绍过程宏。

                          文章转载自最后的叶子的奇妙小屋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                          评论