如何在结构中使用细粒度方法?

时间:2016-10-08 07:21:50

标签: scope rust

我创建了一个小程序,我将用户添加到寄存器中。当我在Java中采用面向对象的方法时,将功能分解为更小的方法是很常见的,因此我为findUser创建了Registerstruct 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

playground

编译错误消息:

{{1}}

2 个答案:

答案 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以编写非常简短易读的算法。
  • 我更改了名称以匹配Rust中的惯用命名:snake_case
  • 每次搜索向量都在O(n)中;它需要线性时间。将n个用户插入寄存器的时间复杂度为O(n²)。这可能不是你想要的。为什么不使用设置数据结构,例如HashSetBTreeSet

答案 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 conventionsnake_case用于方法名称。此外,init可能只应被称为new