【问题标题】:How to implement a macro that defines a new public type and returns an instance of that type?如何实现定义新公共类型并返回该类型实例的宏?
【发布时间】:2020-03-21 06:03:00
【问题描述】:

我想使用macro_rules! 实现一个结构,因为泛型需要大量样板文件和特征搜索。

有问题的结构内部有一个哈希表,但键和值类型由用户提供。代码如下:

macro_rules! new_ytz {
    ($T: ty) => {
        // define the struct
        pub struct Ytz {
            table: hashbrown::hash_map::HashMap<$T, $T>,
        }

        impl Ytz {
            pub fn new() -> Self {
                Ytz {
                    table: hashbrown::hash_map::HashMap::<$T, $T>::new(),
                }
            }

            pub fn add(&mut self, item: &$T) {
                if self.table.contains_key(item) {
                    *self.table.get_mut(item).unwrap() += *item;
                } else {
                    self.table.insert(*item, *item);
                }
            }

            pub fn largest(&self) -> $T {
                let mut result = 0;
                for v in self.table.values() {
                    if result < *v {
                        result = *v;
                    }
                }
                result
            }
        }
        // construct an instance of the struct and return it
        Ytz::new()
    };
}
// driver
fn main() {
    let mut y = new_ytz!(u64); // should construct the object and return Ytz::new()
    y.add(&71);
    y.add(&25);
    y.add(&25);
    y.add(&25);
    y.add(&34);
    println!("{}", y.largest());
}

这不会编译,因为它试图将结构粘贴到主函数中:

error: expected expression, found keyword `pub`
  --> src/main.rs:4:9
   |
4  |         pub struct Ytz {
   |         ^^^ expected expression
...
40 |     let mut y = new_ytz!(u64); // should construct the object and return Ytz::new()
   |                 ------------- in this macro invocation

我该如何解决?如何将结构与 impl 块一起公开粘贴到主函数之外?

【问题讨论】:

  • 你不能这样做。
  • 为什么要让生活变得复杂?
  • 这是因为狩猎特性很难。例如,为了概括这一点,我必须手动查找许多不同类型的特征和大量编译/失败/查找宏/添加特征等。这个 (play.rust-lang.org/…) 看起来很丑,但现在可以完成工作。
  • 特征搜索是什么意思?我可以不用看 Rust 的文档就告诉你T 需要实现HashEqOrd(编译器也会告诉你同样的)。你写它的方式,T 也需要实现Copy,但它可以被修改,所以T 需要Clone。如果您将此作为学习练习,请继续,但这就像使用处理器在 C++ 中实现泛型一样。你可以这样做,但它没有意义。

标签: rust rust-macros rust-decl-macros


【解决方案1】:

泛型需要大量样板

use std::collections::HashMap;
use core::hash::Hash;
use std::ops::AddAssign;

struct YtzU64<T: Eq + Ord + Hash + Copy + AddAssign> {
    table: HashMap<T, T>
}

impl<T: Eq + Ord + Hash + Copy + AddAssign> YtzU64<T> {
    pub fn new() -> Self {
        Self {
            table: HashMap::new()
        }
    }

    pub fn add(&mut self, item: &T) {
        if let Some(item) = self.table.get_mut(item) {
            *item += *item;
        } else {
            self.table.insert(*item, *item);
        }
    }

    pub fn largest(&self) -> Option<T> {
        let mut values = self.table.values();
        let mut largest:Option<T> = values.next().map(|t| *t);
        for v in values {
            if largest < Some(*v) {
                largest = Some(*v);
            }
        }
        largest
    }
}

fn main() {
    let mut y = YtzU64::new();
    y.add(&71);
    y.add(&25);
    y.add(&25);
    y.add(&25);
    y.add(&34);
    println!("{}", y.largest().unwrap());
}

我对您的宏的翻译需要的样板文件比您的宏要少。它少了两个缩进,少了 4 行(macro_rules!,顶部的模式匹配,末尾的两个右大括号)。请注意,我稍微更改了 api,因为 largest 现在返回一个 Option,以匹配 std::iter::Iterator::max()。另请注意,您的 api 设计仅限于T:Copy。如果您想支持T: ?Copy + CloneT: ?Copy + ?Clone,则必须对其进行一些重新设计。

特征狩猎

编译器是你的朋友。观察当我删除一个特征边界时会发生什么

error[E0277]: the trait bound `T: std::hash::Hash` is not satisfied
...

使用宏是一个有趣的练习,但使用宏重新实现泛型没有用。

【讨论】:

  • 技术上,PartialOrd 也应该在这里工作,如果你不关心总排序属性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-13
相关资源
最近更新 更多