Если бы у меня была хэш-карта в Rust, и я хотел бы получить значение и изменить его, например, предположим, что значение имеет тип u32, и я хочу увеличить его, как лучше всего это сделать? Я нашел пример, который работает, но мне интересно, как это сделать с помощью «Лучших практик». Пример, который я нашел, который выполняет эту работу:
`use std::collections::HashMap;
use std::cell::RefCell;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), RefCell::new(0));
let value = map.get("Key").unwrap();
*value.borrow_mut() += 1;
println!("{:?}", value.borrow());
}`
Это сработало для меня, но я с подозрением относился к использованию RefCell для этого. Есть ли лучший способ сделать это? Спасибо.
Да, я тоже сомневаюсь в этом RefCell
. Вы бы использовали это, если бы у вас были очень специфические требования, такие как возможности внутренней изменчивости RecCell
.
Я не понимаю, почему вы не можете просто использовать пример кода и отказаться от RefCell
.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), 0);
let value = map.get_mut("Key").unwrap();
*value += 1;
println!("{:?}", value);
let read_value = map.get("Key").unwrap();
println!("{:?}", read_value);
}
Обратите внимание, дополнительный блок {
просто потому, что я хотел, чтобы value
вышел за рамки, чтобы я мог снова позаимствовать из HashMap. Если у меня его нет, компилятор жалуется, что я заимствовал изменчиво (через get_mut
) и теперь хочу заимствовать неизменно.
FWIW, нелексические времена жизни были стабилизированы в Rust 1.63, поэтому с этого момента вам не нужен дополнительный блок, чтобы явно вывести value
из области видимости.
Это то, о чем я думал; а вот на ржавчине площадка жаловалась. А может у меня опечатка.
Чтобы поделиться примером игровой площадки, вам нужно нажать «Поделиться», а затем отправить постоянную ссылку.
Только что тоже это заметил. Починил это!
Отлично! Я не знаю как, но я совершенно не знал о .get_mut(...). Большое спасибо!!
Добро пожаловать. А для более сложных случаев использования также взгляните на метод entry
. Что-то вроде map.entry("Key".to_string()).or_insert(0).and_modify(|value| *value += 1);
PS: Если вы довольны ответом, вы можете принять его :)
А, спасибо. Я тут давно сижу, только теперь научился задавать вопросы. еще раз спасибо!
Я с подозрением отношусь к этому RefCell
, потому что оно оборачивает целое число — так и должно было быть Cell
:)
RefCell обеспечивает проверку заимствования во время выполнения, в отличие от проверки заимствования во время компиляции, которую вы получили бы в противном случае.
Во многих случаях вам это не нужно — вы можете просто использовать get_mut, как это предлагается в комментариях и @cadolphs.
Однако, если вам нужно одновременно получить изменяемый доступ к отдельным элементам на карте, вы можете использовать RefCell
. Например, рассмотрим этот код:
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), 0);
map.insert("Key2".to_owned(), 1);
let value = map.get_mut("Key").unwrap();
let value2 = map.get("Key2").unwrap();
*value += *value2;
println!("{:?}", *value);
}
Это не удастся скомпилировать, потому что я пытаюсь получить второе значение из хэш-карты, в то время как я все еще держу изменяемую ссылку на первое:
error[E0502]: cannot borrow `map` as immutable because it is also borrowed as mutable
--> src/main.rs:8:18
|
7 | let value = map.get_mut("Key").unwrap();
| ------------------ mutable borrow occurs here
8 | let value2 = map.get("Key2").unwrap();
| ^^^^^^^^^^^^^^^ immutable borrow occurs here
9 | *value += *value2;
| ----------------- mutable borrow later used here
Вы можете решить это с помощью RefCell следующим образом:
use std::collections::HashMap;
use std::cell::RefCell;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), RefCell::new(0));
map.insert("Key2".to_owned(), RefCell::new(1));
let value = map.get("Key").unwrap();
let value2 = map.get("Key2").unwrap();
*value.borrow_mut() += *value2.borrow();
println!("{:?}", *value);
}
Здесь мы можем получить значение, которое мы собираемся изменить, из хэша, используя get
, а не get_mut
, поэтому мы не заимствуем хэш изменяемым образом. Сам хэш не изменяется, только значения внутри него — это шаблон, который в сообществе Rust называют внутренней изменчивостью.
Этот шаблон следует использовать очень экономно, только когда это действительно необходимо.
Во-первых, вы обмениваете проверку во время компиляции на проверку во время выполнения. Если вы допустили ошибку в своей логике, вы не обнаружите это во время компиляции, вы обнаружите, когда код паникует во время выполнения! Вы можете обойти это, используя try_borrow*
версии этих методов (например, try_borrow_mut), которые возвращают Result
вместо паники, но тогда вам нужно добавить обработку ошибок, чтобы справиться с этим.
Другая причина заключается в том, что проверка заимствования во время выполнения может снизить производительность вашего кода.
Мой пример выше — это случай, когда вы можете легко избежать всего этого, потому что значения в хэш-карте — это просто целые числа, которые равны Copy
, поэтому мы можем просто сделать это вместо этого:
let value2 = *map.get("Key2").unwrap();
let value = map.get_mut("Key").unwrap();
*value += value2;
Спасибо, это очень полезно. Когда я разместил исходный вопрос, я не знал о get_mut(), который решил мою проблему. Но я также совершенно не знал о внутренней изменчивости, и я понимаю, что есть целый раздел книги, который я пропустил. Еще раз спасибо!