使用以引用参数为键的函数创建 HashMap

Creating a HashMap with a function with reference paramaters as key

我正在尝试创建一个 HashMap,其中键类型是一个带有引用参数的函数。 let mut hm: HashMap<fn(&u32) -> u32, u32> = HashMap::new();。这工作正常,但我不能在地图中插入任何东西。编译器说这是不合法的,因为不满足特征界限

rules.insert(
  |v: &u32| v + 1,
  0,
);

给予

the method `insert` exists but the following trait bounds were not satisfied:
           `for<'r> fn(&'r u32) -> u32: Eq`
           `for<'r> fn(&'r u32) -> u32: Hash`

我在各种文章中读到过 Lifetimes,但我不知道如何解决这个问题。

背景:我正在实施波函数坍缩。我希望我的实现是通用的,因为可以使用任何“网格”,1d、2d、3d、nd、六角形等。为此,我使用一个“规则集”,它是一个散列图,其中键是一个函数获取一个单元格坐标和 returns 它的邻居,该值是如何折叠所述邻居状态的规则。关键函数采用索引和对“网格”的引用,returns 邻居的索引。

您 运行 遇到的直接问题是 Rust 的 HashMap 要求它的键是 HashEq,并且您提供的闭包没有实现无论是特质。 函数指针 (fn(T, U) -> V) 是 HashEq,但不如闭包灵活。

即使闭包可以实现 HashEq,您也无法将它们用作 HashMap 键。来自 the Rust reference, §10.1.12:

A closure expression produces a closure value with a unique, anonymous type that cannot be written out. [emphasis added]

换句话说,没有两个闭包可以具有相同的类型,即使它们的内容相同!因此,您尝试添加的任何两个闭包的类型按键会冲突。


我不太熟悉波函数坍缩(你在评论中提到),但这里的解决方案可能是使用某种类型将函数描述为键,而不是函数本身。以这个非常简单的例子为例:

#[derive(PartialEq, Eq, Hash)]
pub struct KeyFunction {
    to_add: i32,
}

impl KeyFunction {
    pub fn apply(&self, param: &i32) -> i32 {
        *param + self.to_add
    }
}

type Ruleset<V> = HashMap<KeyFunction, V>;

这将根据 KeyFunction 类型存储的信息限制您可以在特定规则集中使用的键。但是,您可以使用特征来泛化不同类型的键函数 (playground link):

use std::collections::HashMap;

pub trait KeyFunction {
    type Coord;

    fn apply(&self, input: &Self::Coord) -> Self::Coord;
}

/// Descriptor type for simple, contextless 2-D key functions.
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct D2KeyFunction {
    x_ofs: i32,
    y_ofs: i32,
}

impl KeyFunction for D2KeyFunction {
    type Coord = (i32, i32);

    fn apply(&self, input: &Self::Coord) -> Self::Coord {
        (input.0 + self.x_ofs, input.1 + self.y_ofs)
    }
}

type D2Ruleset<V> = HashMap<D2KeyFunction, V>;

这允许您在 Rust 的类型系统中工作时拥有灵活的键函数,并且还强制执行不兼容键函数之间的边界。

如果你想支持多个网格实现,最自然的方法是定义一个 Grid 特征来抽象网格之间的差异。这是我根据您的用例描述粗略编造的:

enum CollapseRule {
    A,
    B,
}

trait Grid {
    const COLLAPSE_RULE: CollapseRule;

    fn neighbours(&self, index: usize) -> Vec<usize>;
}

#[derive(Clone, Debug, Default)]
struct Grid1d {
    vertices: Vec<f64>,
}

impl Grid for Grid1d {
    const COLLAPSE_RULE: CollapseRule = CollapseRule::A;

    fn neighbours(&self, index: usize) -> Vec<usize> {
        let len = self.vertices.len();
        match index {
            0 => vec![1],
            _ if index < len => vec![index - 1, index + 1],
            _ if index == len => vec![index - 1],
            _ => panic!(),
        }
    }
}

无论您的代码需要在何处接受网格,您都可以接受具有特征绑定 G: Grid 的泛型类型 G,并且与网格的任何交互都是通过特征进行的。您的特征可能需要比我的简单示例更多的功能。