Rust:递归函数中可变引用的使用

6

我正在尝试使用递归函数打印Vec的唯一连续子数组,代码如下:

use std::collections::HashSet;

fn recurse<'a>(nums: &'a [i32], already_printed: &'a mut HashSet<&'a [i32]>) {
    if !already_printed.contains(nums) {
        println!("{:#?}", nums);
    }

    already_printed.insert(nums);

    if nums.len() >= 2 {
        recurse(&nums[0..nums.len() - 1], already_printed);
        recurse(&nums[1..nums.len()], already_printed);
    }
}

pub fn main() {
    let k = vec![1, 2, 3, 4, 5];
    let mut already_printed: HashSet<&[i32]> = HashSet::new();
    recurse(&k[0..], &mut already_printed);
}

当然,有经验的Rustaceans可能已经猜到了,这段代码无法通过编译,会出现以下错误:

error[E0499]: cannot borrow `*already_printed` as mutable more than once at a time
  --> src/main.rs:12:39
   |
3  | fn recurse<'a>(nums: &'a [i32], already_printed: &'a mut HashSet<&'a [i32]>) {
   |            -- lifetime `'a` defined here
...
11 |         recurse(&nums[0..nums.len() - 1], already_printed);
   |         --------------------------------------------------
   |         |                                 |
   |         |                                 first mutable borrow occurs here
   |         argument requires that `*already_printed` is borrowed for `'a`
12 |         recurse(&nums[1..nums.len()], already_printed);
   |                                       ^^^^^^^^^^^^^^^ second mutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.

我很清楚从非常有帮助的错误信息中,编译器为什么拒绝编译这个代码。不过,一般来说,有什么解决途径可以实现像上面代码展示的接受可变引用的递归函数呢?

我能想到的一个可能的解决方法是使用内部可变性模式,像RefCell那样:

use std::cell::RefCell;
use std::collections::HashSet;

fn recurse<'a>(nums: &'a [i32], already_printed: &'a RefCell<HashSet<&'a [i32]>>) {
    if !already_printed.borrow().contains(nums) {
        println!("{:#?}", nums);
    }

    already_printed.borrow_mut().insert(nums);

    if nums.len() >= 2 {
        recurse(&nums[0..nums.len() - 1], already_printed);
        recurse(&nums[1..nums.len()], already_printed);
    }
}

pub fn main() {
    let k = vec![1, 2, 3, 4, 5];
    let already_printed: HashSet<&[i32]> = HashSet::new();
    let ref_cell: RefCell<HashSet<&[i32]>> = RefCell::new(already_printed);
    recurse(&k[0..], &ref_cell);
}

虽然这样做有效,但似乎忽略了编译时借用检查器提供的安全保障。是否有一种不同的规范方法来实现上述递归函数调用,同时确保编译时借用检查器通过?

1个回答

8
神奇的解决方案是将函数的声明更改为:
fn recurse<'a, 'b>(nums: &'a [i32], already_printed: &'b mut HashSet<&'a [i32]>) {
// I just changed this lifetime -----------------------^

你的算法没有深层次的错误,只是因为在没有必要的情况下添加了太多的约束。

当你从recurse中再次调用recurse时,完全没有问题,因为它在调用结束时释放了所有权。所以它应该能工作。

但是你要求所有生命周期相同,而实际上可以让编译器确定真正的约束。


感谢您的快速回复。 - balajeerc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接