我创建了一个小程序,我将用户添加到寄存器中。当我在Java中采用面向对象的方法时,将功能分解为更小的方法是很常见的,因此我为findUser
创建了Register
和struct User {
userId: u8,
age: u8,
}
struct Register {
users: Vec<User>,
}
impl Register {
// Initialize
fn init() -> Register {
Register {
users: Vec::new()
}
}
// Add user to register, if user does not exist in register
fn addUser(&mut self, user: User) {
match self.findUser(user.userId) {
Some(u) => println!("User exists!"),
None => self.users.push(user),
};
}
// If user exists, return user, else return None
fn findUser(&self, userId: u8) -> Option<&User> {
for user in &self.users {
if user.userId == userId {
return Some(&user);
}
}
None
}
}
方法。当我编译这个程序时,我得到以下编译错误。我读过有关借用的内容,但无法找到解决此问题的方法。
error[E0502]: cannot borrow `self.users` as mutable because `*self` is also borrowed as immutable
--> src/main.rs:18:21
|
16 | match self.findUser(user.userId) {
| ---- immutable borrow occurs here
17 | Some(u) => println!("User exists!"),
18 | None => self.users.push(user),
| ^^^^^^^^^^ mutable borrow occurs here
19 | };
| - immutable borrow ends here
编译错误消息:
{{1}}
答案 0 :(得分:1)
这是一个老问题,有一天它会被非词汇生命期解决。你的程序是安全的,编译器就是无法看到它(还是!)。
有很多方法可以解决这个问题;这是一种可能性(playground):
fn add_user(&mut self, user: User) {
let user_pos = self.users.iter().position(|u| u.user_id == user.user_id);
match user_pos {
Some(pos) => {
let u = &self.users[pos]; // get the user like that
println!("User exists!");
}
None => self.users.push(user),
}
}
关键洞察力:我们首先在向量中找到位置,然后使用该位置以后来为向量编制索引。这个延迟引用向量中的内容。
一些补充说明:
findUser
方法:它是一种超常用的算法,因此已有快捷方式。我建议您熟悉Iterator
API以编写非常简短易读的算法。snake_case
。HashSet
或BTreeSet
?答案 1 :(得分:1)
rustc
抱怨在这种情况下要做的第一件事当然是理解为什么rustc
认为你所做的事情是不允许的。那么让我们来看看你想要做什么:
Register
包含User
s的向量。 findUser
返回对该向量的引用。 addUser
将新的User
推送到该向量中。到现在为止还挺好。 addUser
使用findUser
查找Option<&User>
。对User
的(可选)引用是指User
向量中的users
。如果找到匹配的User
,则u
中的引用指向有效的User
,但您忽略它。如果找不到匹配的User
,则不存在引用,但是您会改变向量。当然,推入向量可能需要重新分配,这会使对向量的引用无效。但是,这不是问题,因为此时不存在对向量的引用。
但这是rustc
(或更具体地说:借用者)不足之处:当借款人看到
match self.findUser(user.userId){
// ...
};
它的结论是Option<&User>
必须为整个match
正文生效,并得出结论:&User
必须为整个match
正文生效。因此,当您尝试在self.users
身体内推进match
时,它会抱怨。
一种解决方案是等待借用程序变得更聪明,并确定匹配Option<T>
可以忽略T
分支中None
的生命周期。
一个更好的短期解决方案是通过拆分检查和推送,例如,通过拆分检查和借助,向借方检查员煞费苦心地解释它是好的。如下:
let is_found = match self.findUser(user.userId){
Some(u) => { println!("User exists!"); true},
None => false
};
if !is_found {
self.users.push(user)
}
另一个解决方案使用了这样一个事实:你根本就没有使用&User
,所以你可以写下以下内容:
if self.findUser(user.userId) // At this point there is still a `&User`
.is_some() { // Here, it's lifetime is over and there's a `bool` instead
println!("User exists!")
} else {
// No live references into `self.user` exist, mutation is trivially allowed
self.users.push(user)
}
另一个解决方案是更改Register
的API,以反映您不想对匹配的用户执行任何操作:只需创建fn contains_user(&self, userId: u8) -> bool
。
顺便说一句,正如@starblue在评论中已提到的那样,Rust naming convention将snake_case
用于方法名称。此外,init
可能只应被称为new
。