Rust HashMap поддерживает стабильность указателя, позволяя ключу ссылаться на его значение

В Rust использование HashMap, где ключом является &str, который ссылается на член String внутри соответствующего значения, напрямую невозможно. Следующий пример иллюстрирует эту идею:

type PersonMap = HashMap<&str, Person>;

struct Person {
    name: String,
}

Это невозможно из-за правил владения и заимствования Rust, а также изменения размера HashMap, как объяснено в этом ответе (https://stackoverflow.com/a/61021022/84540).

Однако мне интересно, существует ли реализация неупорядоченной карты, поддерживающая стабильность указателя (Что такое стабильность указателя?), которая могла бы сделать такое использование возможным. Или это невозможно из-за пожизненной проверки Руста? Спасибо.

Что произойдет, если кто-то изменит значение на месте с помощью get_mut?

user3840170 19.06.2024 18:20
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
3
1
86
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Теоретически даже String обеспечивает стабильность указателя, поскольку при его перемещении его содержимое не будет перемещаться (хотя вопросы псевдонимов все еще остаются под вопросом).

На практике стабильности указателя недостаточно. Достаточно, чтобы код не имел UB, но недостаточно, чтобы он прошел проверку заимствований. Средство проверки заимствований не допускает самоссылающихся структур и не заботится о стабильности указателя.

Я не имею в виду, что карту, допускающую самореферентные структуры, невозможно написать (вероятно, можно, но в ней будет много дыр в надежности). Возможно, это даже было уже написано. Но если такая карта существует, ее нужно будет написать с самого начала, чтобы иметь дело с самореферентной картой. Это не «просто еще одна карта», поддерживающая ключи, ссылающиеся на значения, по сути, это ее цель.

Если вы ищете, как написать такую ​​карту, вы можете черпать вдохновение из множества существующих самоссылающихся ящиков. Я предполагаю, что такая карта будет иметь дизайн, похожий на одну из них, просто адаптированный к карте, а не к общей структуре.

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

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

Вот как это может выглядеть:

use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::collections::HashSet;

#[derive(Debug)]
struct Person {
    name: String,
    // other fields
}

#[derive(Debug)]
struct PersonByName(Person);

impl PartialEq for PersonByName {
    fn eq(&self, other: &Self) -> bool {
        self.0.name == other.0.name
    }
}

impl Eq for PersonByName {}

impl Hash for PersonByName {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.name.hash(state);
    }
}

impl Borrow<str> for PersonByName {
    fn borrow(&self) -> &str {
        &self.0.name
    }
}

fn main() {
    let people = vec![
        Person { name: "Jimmy".to_owned() },
        Person { name: "Timmy".to_owned() },
        Person { name: "Lenny".to_owned() },
    ];
    
    let map: HashSet<PersonByName> = people.into_iter().map(PersonByName).collect();
    
    println!("{:?}", map.get("Timmy"));
}
Some(PersonByName(Person { name: "Timmy" }))

Ссылка на игровую площадку

Таким образом, даже несмотря на то, что это «набор», а не «карта», мы можем обращаться с ним как с картой, используя свойство Rust Borrow при .get вводе значения. Hash и PartialEq/Eq нужны для того, чтобы он вел себя как простая строка, чтобы поиск HashSet был последовательным.

Это классная идея. Никогда об этом не думал. Спасибо.

nybon 20.06.2024 09:13

Причина недействительности в том, что составитель не знает, сколько проживет владелец. По этой причине он считает ссылку недействительной. Вы можете заставить компилятор (с помощью концепции времени жизни) учитывать, что две ссылки имеют одинаковое время жизни. Пример:

use std::collections::HashMap;
struct Person {
    name: String,
}
impl Person {
    fn new(name: String) -> Self {
        Self { name }
    }
    fn ref_name(&self) -> &str {
        &self.name
    }
}
type PersonMap<'a> = HashMap<&'a str, &'a Person>;
fn main() {
    let mut person_map: PersonMap = HashMap::new();
    let person = Person::new("name".to_string());
    person_map.insert(person.ref_name(), &person);
}

Это не отвечает на вопрос: ОП хочет хранить сами Person в HashMap (т. е. HashMap<_, Person>), но вы сохраняете только ссылки на Person (т. е. HashMap<_, &Person>)

Jmb 19.06.2024 20:46

Как уже упоминалось, это невозможно, поскольку даже если бы адрес был стабильным, этого было бы недостаточно, чтобы гарантировать, что строка никогда не будет освобождена, пока существует ссылка.

Но в подобных ситуациях, когда вы хотите использовать одно и то же значение в двух местах, возможно совместное владение; вы бы использовали Arc<str> или Rc<str> вместо String.

use std::collections::HashMap;
use std::sync::Arc;

pub type PersonMap = HashMap<Arc<str>, Person>;

pub struct Person {
    name: Arc<str>,
}

pub fn insert(map: &mut PersonMap, person: Person) {
    map.insert(person.name.clone(), person);
}

Это не так эффективно, как опция PersonByName в ответе kmdreko, поскольку она хранит дополнительные указатели, но она проста и применима к широкому спектру возможных структур данных.

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