recursion - Rust:在递归函数中使用可变引用

标签 recursion rust borrow-checker

我正在尝试使用递归函数打印 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`.

我从非常有用的错误中理解为什么编译器拒绝编译它。但是,一般来说,实现上述代码中采用可变引用的递归函数的解决方法是什么?

我能想到的一种可能的解决方法是使用 interior mutability pattern à la 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);
}

虽然这可行,但这似乎放弃了编译时借用检查器提供的安全轨。是否有一种不同的规范方法可以像上面那样进行递归函数调用,同时仍然有编译时借用检查器通过?

最佳答案

神奇的解决方法是将函数的声明改为

fn recurse<'a, 'b>(nums: &'a [i32], already_printed: &'b mut HashSet<&'a [i32]>) {
// I just changed this lifetime -----------------------^

您的算法无法正常工作并没有深层原因,只是您在没有理由的情况下添加了太多约束。

当您从 recurse 内部调用 recurse 时,这完全没问题,因为它在调用结束时释放了所有权。所以它必须工作。

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

关于recursion - Rust:在递归函数中使用可变引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67130206/

相关文章:

rust - 如何在 Rust 中将字符串更改为数组

rust - 嵌套数组索引中的 "cannot borrow as immutable because it is also borrowed as mutable"是什么意思?

javascript - 难以在 JavaScript 中实现简化的借用检查器

c - 如何通过指向结构的指针间接释放结构内数组中的指针并将其写入 NULL?

java - java中递归方法的时间限制

vb.net 仅限外部的属性或方法

rust - 有没有办法在 futures for_each 流中继续?

asynchronous - 为什么在使用带有 std::sync::Mutex 的 Tokio 时会出现死锁?

java - 如何在java中创建自上而下的重载构造函数

rust - 借用 Rust 中的检查器和函数参数,正确还是过于热心?