У меня есть структуры Ценность и RefValue в моем проекте. RefValue — это динамически заимствуемый Ценность с подсчетом ссылок. Теперь Ценность может содержать HashMap RefValue, где и ключ, и значение являются 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>>,
}
Я самостоятельно реализовал Hash на RefValue и некоторые From-черты отдельно в этом детская площадка.
Чего я хочу добиться, так это что-то вроде этой основной программы:
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
};
}
К сожалению, я получаю странные результаты, как вы можете найти в комментариях на игровой площадке. Я также совершенно не уверен, что нет лучшей реализации всей проблемы, поскольку RefCell не может вернуть заимствованное значение содержащегося в нем элемента.
Может ли кто-нибудь дать мне подсказку?
Ключи не должны иметь внутренней изменяемости. Изменение ключей во время их добавления на карту приводит к непредсказуемым результатам, и с Rc
будет невероятно легко сделать это случайно, поскольку код, который изменяет значение, может даже не знать, что он используется в качестве ключа карты.
Когда вы реализуете Borrow<T>
, ваша реализация Hash
должен возвращает то же хеш-значение, что и T
, когда базовое значение эквивалентно. То есть, если x.hash()
должен будет равно x.borrow().hash()
. HashMap
опирается на это свойство, когда вы индексируете его: оно требует Idx: Borrow<Key>
, а затем использует это правило, чтобы убедиться, что оно может найти значение.
Ваш impl Borrow<str> for RefValue
не следует этому правилу. RefValue::hash()
for RefValue::String
вызывает write_u8(2)
перед хешированием строки. Поскольку вы нарушили контракт, хэш-карте разрешено делать что угодно (за исключением неопределенного поведения), например паниковать, прерывать процесс или не находить ваш ключ, что он и делает в данном случае.
Чтобы исправить это, вы должны просто не хэшировать дискриминант (удалите его и из других, для согласованности):
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
, как вы и ожидали (детская площадка).
Но вы не должны внедрять Borrow
, поскольку его реализация означает, что ваша ценность является отражением заимствованной ценности. RefValue
ни в коем случае не str
. Это могут быть целые числа или карты. Таким образом, вы не должны реализовывать Borrow
ни для одного из них. Вы можете реализовать Borrow<Value>
, но это невозможно, потому что вы используете RefCell
и, следовательно, должны вернуть Ref
, но Borrow
требует возврата ссылки. Не повезло тебе. Единственный вариант — индексировать с помощью RefValue
s.
Наконец, вы должны избегать внутренней изменчивости ключей. Один раз измените их, а их легко изменить по ошибке, и ваш хеш/равенство изменится, вы снова нарушили свой контракт с картой.
Привет @chayim-friedman, спасибо за разъяснение проблемы. Я был тупицей в этом. Во всяком случае, для уточнения: нет ли способа получить запись HashMap только по ее чистому хеш-значению, которое может исходить либо из &str, либо из строки внутри значения внутри RefValue? Затем я мог бы добавить функцию «get_by_str» для индексации ValueMap с помощью &str?
@glasflügel Не в стандарте. Вы можете использовать крейт hashbrown напрямую и необработанный API входа, но совсем не очевидно, что он будет добавлен в std.
Работает с
m[&RefValue::from("x")]