How to fix closure requires unique access to
*selfbut it is already borrowed
?
I see the error for this code:
error[E0500]: closure requires unique access to `*self` but it is already borrowed
--> chat/src/user.rs:47:23
|
| return self
| ________________-
| | .token_to_user_id
| |_____________________________- borrow occurs here
| .get_mut(token)
| .and_then(|id| self.find_by_id_mut(id));
| -------- ^^^^ ---- second borrow occurs due to use of `*self` in closure
| | |
| | closure construction occurs here
| first borrow later used by call
I do understand what the error says and that my code is breaking the rules of borrowing because it has two mutable borrows, however, I don't understand how should code in Rust be written to handle methods on data structures which at first read something, and then mutate it. How to solve such situations?
use std::collections::HashMap;
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
pub struct UserId(pub u32);
#[derive(Default)]
pub struct UserIndex {
admin: User,
id_to_user: HashMap<UserId, User>,
name_to_user_id: HashMap<String, UserId>,
token_to_user_id: HashMap<String, UserId>,
}
impl UserIndex {
pub fn insert_user(&mut self, user: User) {
let id = user.id;
self.id_to_user.insert(id, user.clone());
self.token_to_user_id.insert(user.token.clone(), id);
match user.user_name {
None => {
self.admin = user;
}
Some(name) => {
self.name_to_user_id.insert(name, id);
}
}
}
pub fn ban_user(&mut self, id: UserId) -> bool {
if let Some(user) = self.find_by_id_mut(&id) {
user.is_banned = true;
return true;
}
return false;
}
fn find_by_id_mut(&mut self, id: &UserId) -> Option<&mut User> {
return self.id_to_user.get_mut(id);
}
fn find_by_token_mut(&mut self, token: &String) -> Option<&mut User> {
return self
.token_to_user_id
.get_mut(token)
.and_then(|id| self.find_by_id_mut(id));
}
}
#[derive(Default, Clone)]
pub struct User {
pub id: UserId,
// admin doesn't have name and password
pub user_name: Option<String>,
pub password: Option<String>,
pub token: String,
pub is_banned: bool,
}
Answers
In Rust, you can't have two mutable borrows of the same object at the same time. This rule is enforced by the borrow checker to prevent data races and ensure memory safety.
To solve this issue, you can use a different approach to avoid borrowing self
mutably twice in the same scope. One common approach is to split the mutable borrow into two separate scopes, ensuring that they don't overlap.
Here's how you can refactor your code to achieve this:
impl UserIndex {
// Other methods...
pub fn ban_user(&mut self, id: UserId) -> bool {
// Borrow self once to get a mutable reference to the user by id
if let Some(user) = self.find_by_id_mut(&id) {
// Inside this scope, self is not borrowed anymore
// You can safely mutate the user
user.is_banned = true;
return true;
}
return false;
}
fn find_by_token_mut(&mut self, token: &String) -> Option<&mut User> {
// Borrow self once to get a mutable reference to the user id by token
if let Some(id) = self.token_to_user_id.get(token) {
// Borrow self again to get a mutable reference to the user by id
// Since the first borrow is dropped, self is not borrowed anymore
if let Some(user) = self.find_by_id_mut(id) {
return Some(user);
}
}
None
}
}
In this refactored code:
- In the
ban_user
method, we borrowself
once to get a mutable reference to the user by id. Inside the scope of this borrow, we can safely mutate the user. After this scope,self
is not borrowed anymore. - In the
find_by_token_mut
method, we borrowself
twice in separate scopes. First, we borrowself
to get a mutable reference to the user id by token. Then, inside a new scope, we borrowself
again to get a mutable reference to the user by id.
By splitting the mutable borrows into separate scopes, we ensure that self
is not borrowed mutably at the same time in the same scope, resolving the borrow checker error.