Как изменить значения в HashMap в Rust?

Если бы у меня была хэш-карта в 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 для этого. Есть ли лучший способ сделать это? Спасибо.

HashMap::get_mut возвращает изменяемую ссылку, которую можно использовать для изменения базового значения.
EvilTak 10.02.2023 21:06
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
1
61
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Да, я тоже сомневаюсь в этом 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);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a23a90c4df7eb010945980b9b95eb031

Обратите внимание, дополнительный блок { просто потому, что я хотел, чтобы value вышел за рамки, чтобы я мог снова позаимствовать из HashMap. Если у меня его нет, компилятор жалуется, что я заимствовал изменчиво (через get_mut) и теперь хочу заимствовать неизменно.

cadolphs 10.02.2023 21:10

FWIW, нелексические времена жизни были стабилизированы в Rust 1.63, поэтому с этого момента вам не нужен дополнительный блок, чтобы явно вывести value из области видимости.

EvilTak 10.02.2023 21:14

Это то, о чем я думал; а вот на ржавчине площадка жаловалась. А может у меня опечатка.

cadolphs 10.02.2023 21:16

Чтобы поделиться примером игровой площадки, вам нужно нажать «Поделиться», а затем отправить постоянную ссылку.

jthulhu 10.02.2023 21:16

Только что тоже это заметил. Починил это!

cadolphs 10.02.2023 21:17

Отлично! Я не знаю как, но я совершенно не знал о .get_mut(...). Большое спасибо!!

astrobuddha 10.02.2023 22:50

Добро пожаловать. А для более сложных случаев использования также взгляните на метод entry. Что-то вроде map.entry("Key".to_string()).or_insert(0).and_modify(|value| *value += 1);

cadolphs 10.02.2023 23:11

PS: Если вы довольны ответом, вы можете принять его :)

cadolphs 10.02.2023 23:11

А, спасибо. Я тут давно сижу, только теперь научился задавать вопросы. еще раз спасибо!

astrobuddha 11.02.2023 17:48

Я с подозрением отношусь к этому RefCell, потому что оно оборачивает целое число — так и должно было быть Cell :)

Chayim Friedman 11.02.2023 19:10

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(), который решил мою проблему. Но я также совершенно не знал о внутренней изменчивости, и я понимаю, что есть целый раздел книги, который я пропустил. Еще раз спасибо!

astrobuddha 11.02.2023 17:46

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