Сначала несколько комментариев о том, что у вас есть и почему это не работает. root
должен быть изменяемой ссылкой. Обратите внимание на различие между изменяемой переменной (let mut root = &db.children;
) и изменяемой ссылкой (let root = &mut db.children;
). Первый позволяет изменять саму переменную. Последнее позволяет изменять данные за ссылкой. В этом случае нам нужны оба (let mut root = &mut db.children
), потому что мы не только изменяем root
при выполнении итерации по узлам, но также изменяем данные за ссылкой, когда нам нужно вставить новый узел.
То же самое относится к d
во внутренней l oop (это должна быть изменяемая переменная), хотя, как мы увидим, мутирование d
не совсем то, что мы хотим.
// if key doesnt exist add it with a ne empty hashmap
let d = Database{children: HashMap::new(), data: "".to_string()};
// set root to this new databse obejct
root = &mut d.children;
root.insert(subkey.to_string(), d);
На мгновение игнорируя ошибки, что должен этот код делать? d
- это новый Database
без реальных данных. Затем мы устанавливаем root
как (пустой) набор дочерних элементов этого нового Database
. Наконец, мы вставляем новый Database
в root. Но поскольку мы изменили root
на втором шаге, он больше не является родительским: мы вставляем d
как дочерний элемент!
Вместо этого мы хотим изменить порядок вторых двух шагов. Но если мы просто переключим эти две строки, мы получим ошибку
error[E0382]: borrow of moved value: `d`
--> src/main.rs:41:24
|
36 | let mut d = Database{children: HashMap::new(), data: "".to_string()};
| ----- move occurs because `d` has type `Database`, which does not implement the `Copy` trait
37 |
38 | root.insert(subkey.to_string(), d);
| - value moved here
...
41 | root = &mut d.children;
| ^^^^^^^^^^^^^^^ value borrowed here after move
Так что проблема в том, что d
больше не является локальной переменной, когда мы пытаемся установить root
для ее дочерних элементов. Нам нужно root
, чтобы быть потомками только что вставленного значения. Обычная идиома для такого рода вещей - entry
API . Это позволяет нам пытаться получить значение из HashMap
и, если оно не найдено, вставить что-то. Наиболее уместно, что эта вставка возвращает изменяемую ссылку на любое значение, которое теперь находится в этом ключе.
Теперь этот раздел выглядит как
// if key doesnt exist add it with a new empty hashmap
let d = Database{children: HashMap::new(), data: "".to_string()};
// insert the new database object and
// set root to the hashmap of children
root = &mut root.entry(subkey.to_string()).or_insert(d).children;
На данный момент у нас есть явно работающая программа . Добавив #[derive(Debug)]
к Database
, мы можем увидеть, как выглядит база данных с println!("{:#?}, db);
. Однако, если мы попытаемся добавить второе значение, все взрывается. Вместо того чтобы размещать два значения рядом, они оказываются в совершенно разных ветвях базы данных. Это восходит к закомментированным строкам в ветке Some(child)
оператора match.
Мы бы хотели установить root
в изменяемую ссылку на child.children
, но даже просто раскомментировать эту строку без любые изменения приводят к ошибке, что root
является заимствованным заимствованным, в то время как заимствовано в другом месте. Проблема в том, что мы используем заем в root.get(&subkey.to_string())
сейчас. Раньше, поскольку мы игнорировали child
, а другая ветвь не использовала никаких данных из этого заимствования, заимствование могло закончиться немедленно. Теперь это должно длиться на протяжении всего матча. Это не позволяет нам брать заимствования даже в случае None
.
К счастью, поскольку мы используем entry
API, нам вообще не нужен этот оператор сопоставления! Все это можно просто заменить на
let d = Database {
children: HashMap::new(),
data: "".to_string(),
};
// insert the new database object and
// set root to the hashmap of children
root = &mut root.entry(subkey.to_string()).or_insert(d).children;
Если подраздел уже существует в наборе дочерних элементов, root.entry(...).or_insert(...)
будет указывать на этого уже существующего дочернего элемента.
Теперь нам просто нужно очистить код. Так как вы используете его более одного раза, я бы порекомендовал учесть факт вставки пути клавиш в функцию. Вместо того, чтобы следовать по пути HashMap<String, Database>
, я бы рекомендовал следовать самому Database
, поскольку это позволит вам изменить его поле data
в конце. Для этого я бы предложил функцию с такой подписью:
impl Database {
fn insert_path(&mut self, path: &[&str]) -> &mut Database {
todo!()
}
}
Далее, поскольку нам нужно создать новый Database
(d
) только тогда, когда он еще не существует, мы может использовать Entry
метод or_insert_with
для создания новой базы данных только при необходимости. Это проще всего, когда есть функция для создания новой базы данных, поэтому давайте добавим #[derive(Default)]
в список производных на Database
. Это делает нашу функцию
impl Database {
fn insert_path(&mut self, path: &[&str]) -> &mut Self {
let mut root = self;
// iterate throught path
for subkey in path.iter() {
// insert the new database object if necessary and
// set root to the hashmap of children
root = root
.children
.entry(subkey.to_string())
// insert (if necessary) using the Database::default method
.or_insert_with(Database::default);
}
root
}
}
На этом этапе мы должны запустить cargo clippy
, чтобы увидеть, есть ли какие-либо предложения. Есть один способ использования to_string
на &&str
. Чтобы это исправить, у вас есть два варианта. Во-первых, используйте один из других методов для преобразования &str
с String
с вместо to_string
. Во-вторых, разыменуйте &&str
перед использованием to_string
. Этот второй вариант проще. Поскольку мы перебираем &[&str]
(Vec<&str>::iter
в вашем оригинале), элементы в итерации равны &&str
. Идиоматический c способ избавиться от дополнительного слоя ссылок - использовать шаблон для деструктурирования элементов.
for &subkey in path {
^^^ this is new
... // subkey has type &str instead of &&str here
}
Мой последний совет - изменить имя root
на что-то более универсальный c, как node
. Это только root в самом начале, поэтому имя вводит в заблуждение после этого. Вот окончательный код вместе с вашими тестами (детская площадка) :
use std::collections::HashMap;
#[derive(Default, Debug)]
struct Database {
children: HashMap<String, Database>,
data: String,
}
impl Database {
fn insert_path(&mut self, path: &[&str]) -> &mut Self {
// node is a mutable reference to the current database
let mut node = self;
// iterate through the path
for &subkey in path.iter() {
// insert the new database object if necessary and
// set node to (a mutable reference to) the child node
node = node
.children
.entry(subkey.to_string())
.or_insert_with(Database::default);
}
node
}
}
fn main() {
// make a databse object
let mut db = Database {
children: HashMap::new(),
data: "root".to_string(),
};
// some example subkeys
let subkeys = vec!["key1", "key1.1", "key1.1.3"];
// and the value i want to insert
let value = "myvalue";
let node = db.insert_path(&subkeys);
node.data = value.to_string();
println!("{:#?}", db);
let subkeys = vec!["key1", "key1.1", "key1.1.2"];
let value = "myvalue2";
let node = db.insert_path(&subkeys);
node.data = value.to_string();
println!("{:#?}", db);
}