Реализовать заимствование чего-то позади RefCell?

У меня есть структуры Ценность и 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 не может вернуть заимствованное значение содержащегося в нем элемента.

Может ли кто-нибудь дать мне подсказку?

Работает с m[&RefValue::from("x")]

Alexey Larionov 23.04.2022 14:46

Ключи не должны иметь внутренней изменяемости. Изменение ключей во время их добавления на карту приводит к непредсказуемым результатам, и с Rc будет невероятно легко сделать это случайно, поскольку код, который изменяет значение, может даже не знать, что он используется в качестве ключа карты.

cdhowie 23.04.2022 15:18
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
2
30
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Когда вы реализуете 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 требует возврата ссылки. Не повезло тебе. Единственный вариант — индексировать с помощью RefValues.

Наконец, вы должны избегать внутренней изменчивости ключей. Один раз измените их, а их легко изменить по ошибке, и ваш хеш/равенство изменится, вы снова нарушили свой контракт с картой.

Привет @chayim-friedman, спасибо за разъяснение проблемы. Я был тупицей в этом. Во всяком случае, для уточнения: нет ли способа получить запись HashMap только по ее чистому хеш-значению, которое может исходить либо из &str, либо из строки внутри значения внутри RefValue? Затем я мог бы добавить функцию «get_by_str» для индексации ValueMap с помощью &str?

glasflügel 25.04.2022 21:37

@glasflügel Не в стандарте. Вы можете использовать крейт hashbrown напрямую и необработанный API входа, но совсем не очевидно, что он будет добавлен в std.

Chayim Friedman 25.04.2022 23:42

Другие вопросы по теме