在 RefCell 后面的东西上实现借用?

Implement Borrow on something behind a RefCell?

我的项目中有结构 ValueRefValueRefValue 是一个引用计数的、动态可借用的 Value。现在 Value 可能包含 RefValue 的 HashMap,其中键和值都是 RefValue。

type ValueMap = HashMap<RefValue, RefValue>;

#[derive(Debug, PartialEq, Eq)]
enum Value {
    Integer(i64),
    String(String),
    Map(ValueMap),
}

#[derive(Debug, PartialEq, Eq)]
struct RefValue {
    value: Rc<RefCell<Value>>,
}

我自己在 RefValue 上实现了 Hash,在这个 playground.

中单独实现了一些 From-traits

我想要实现的是这样的主程序:

fn main() {
    // Simple values
    let x = RefValue::from(42);
    let y = RefValue::from("Hello");

    // Make a map from these values
    let mut z = ValueMap::new();
    z.insert(RefValue::from("x"), x);
    z.insert(RefValue::from("y"), y);

    // Make a value from the map
    let z = RefValue::from(z);
    println!("z = {:?}", z);

    // Try to access "x"
    if let Value::Map(m) = &*z.borrow() {
        println!("m[x] = {:?}", m["x"]);  // <- here I want to access by &str
    };
}

不幸的是,我得到了奇怪的结果,您可以在 playground 评论中找到。我也很不确定是否没有更好的方法来解决整个问题,因为 RefCell 不能 return 其包含元素的借用值。

有人可以给我提示吗?

当您实施 Borrow<T>your Hash implementation must return the same hash value as T's for when the underlying value is equivalent 时。即如果x.hash()一定等于x.borrow().hash()HashMap 在索引时依赖于此 属性:它需要 Idx: Borrow<Key> 然后使用此规则来确保它可以找到该值。

您的 impl Borrow<str> for RefValue 不遵守此规则。 RefValue::hash() for RefValue::String 在散列字符串之前调用 write_u8(2)。因为你违反了合同,hashmap 可以做任何事情(不包括未定义的行为),比如恐慌、中止进程或找不到你的密钥,这就是它在这种情况下所做的。

要解决这个问题,您不应该散列判别式(为了保持一致性,也将其从其他判别式中删除):

impl Hash for RefValue {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match &*self.borrow() {
            Value::Integer(i) => {
                i.hash(state);
            }
            Value::String(s) => {
                s.hash(state);
            }
            Value::Map(m) => {
                (m as *const ValueMap as usize).hash(state);  // Object address
            }
        }
    }
}

现在它在您的 Borrow 实施中出现恐慌,正如您预期的那样 (playground)。

但你不应该实施 Borrow,因为实施它意味着你的价值是借来价值的反映。 RefValue 绝不是 str。它也可以是整数或映射。因此,您不应该为其中任何一个实现 Borrow 。您可以实施 Borrow<Value>,但这是不可能的,因为您使用 RefCell 因此需要 return RefBorrow 要求 return 引用.你倒霉了。您唯一的选择是使用 RefValues.

进行索引

最后,您应该避免键的内部可变性。一旦改了,很容易误改,你的hash/equality改,又一次违约