I have the structs Value and RefValue in my project. RefValue is a reference-counted, dynamically borrowable Value. Now Value may contain a HashMap of RefValue, where both the key and the value is a 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>>,
}
I've implemented Hash on RefValue on my own, and some From-traits separately in this playground.
What I want to achieve is something like this main program:
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
};
}
Unfortunately I'm getting strange results, as you can find in the playground comments. I'm also quite unsure if there's not a better implementation of the entire problem, as the RefCell cannot return a borrowed value of its contained element.
Can anybody give me a hint?
When you implement
Borrow<T>
, yourHash
implementation must return the same hash value asT
's for when the underlying value is equivalent. That is, ifx.hash()
must be equal tox.borrow().hash()
.HashMap
relies on this property when you index into it: it requiresIdx: Borrow<Key>
and then uses this rule to ensure it can find the value.Your
impl Borrow<str> for RefValue
does not follow this rule.RefValue::hash()
forRefValue::String
callswrite_u8(2)
before hashing the string. Since you broke the contract, the hashmap is allowed to do anything (excluding undefined behavior), like panicking, aborting the process, or not finding your key, which is what it does in this case.To fix that, you should just not hash the discriminant (removed it from the others too, for consistency):
Now it panics in your
Borrow
implementation, like you expected (playground).But you should not implement
Borrow
, since implementing it means your value is a reflection of the borrowed value.RefValue
is by no meansstr
. It can be integers, or maps, too. Thus, you should not implementBorrow
for any of those. You could implementBorrow<Value>
, but this is impossible because you useRefCell
and thus need to returnRef
butBorrow
mandates returning a reference. You're out of luck. Your only option is to index withRefValue
s.Lastly, you should avoid interior mutability in keys. Once change them, and it's easy to change them by mistake, and your hash/equality change, you broke your contract with the map once again.