В Rust использование HashMap
, где ключом является &str
, который ссылается на член String
внутри соответствующего значения, напрямую невозможно. Следующий пример иллюстрирует эту идею:
type PersonMap = HashMap<&str, Person>;
struct Person {
name: String,
}
Это невозможно из-за правил владения и заимствования Rust, а также изменения размера HashMap
, как объяснено в этом ответе (https://stackoverflow.com/a/61021022/84540).
Однако мне интересно, существует ли реализация неупорядоченной карты, поддерживающая стабильность указателя (Что такое стабильность указателя?), которая могла бы сделать такое использование возможным. Или это невозможно из-за пожизненной проверки Руста? Спасибо.
Теоретически даже 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
был последовательным.
Это классная идея. Никогда об этом не думал. Спасибо.
Причина недействительности в том, что составитель не знает, сколько проживет владелец. По этой причине он считает ссылку недействительной. Вы можете заставить компилятор (с помощью концепции времени жизни) учитывать, что две ссылки имеют одинаковое время жизни. Пример:
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>
)
Как уже упоминалось, это невозможно, поскольку даже если бы адрес был стабильным, этого было бы недостаточно, чтобы гарантировать, что строка никогда не будет освобождена, пока существует ссылка.
Но в подобных ситуациях, когда вы хотите использовать одно и то же значение в двух местах, возможно совместное владение; вы бы использовали 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
, поскольку она хранит дополнительные указатели, но она проста и применима к широкому спектру возможных структур данных.
Что произойдет, если кто-то изменит значение на месте с помощью
get_mut
?