【问题标题】:Rust lifetime issue in loop循环中的 Rust 生命周期问题
【发布时间】:2017-05-23 09:35:53
【问题描述】:

如何在不复制数组或每次迭代多次调用 b() 的情况下编译此示例 - b() 必须执行一些昂贵的解析?

这不是我编写的完整代码,但它说明了我遇到的问题。在这里,Test 正在尝试执行某种流解析工作。 c()是解析函数,解析成功时返回Someb() 是一个函数,当c() 无法使用可用数据进行解析时,它会尝试从流中读取更多数据。返回的值是self.v 中包含已解析范围的切片。

struct Test {
    v: [u8; 10],
    index: u8,
}

impl Test {
    fn b(&mut self) {
        self.index = 1
    }

    fn c(i: &[u8]) -> Option<&[u8]> {
        Some(i)
    }

    fn a(&mut self) -> &[u8] {
        loop {
            self.b();

            match Test::c(&self.v) {
                Some(r) => return r,
                _ => continue,
            }
        }
    }
}

fn main() {
    let mut q = Test {
        v: [0; 10],
        index: 0,
    };

    q.a();
}

编译时,会产生以下借用检查错误:

error[E0502]: cannot borrow `*self` as mutable because `self.v` is also 
borrowed as immutable
  --> <anon>:17:13
   |
17 |             self.b();
   |             ^^^^ mutable borrow occurs here
18 | 
19 |             match Test::c(&self.v) {
   |                            ------ immutable borrow occurs here
...
24 |     }
   |     - immutable borrow ends here

如果我将a() 更改为:

fn a(&mut self) -> Option<&[u8]> {
    loop {
        self.b();

        if let None = Test::c(&self.v) {
            continue
        }

        if let Some(r) = Test::c(&self.v) {
            return Some(r);
        } else {
            unreachable!();
        }
    }
}

然后它运行,但有一个明显的缺点是调用解析函数c() 两次。

我有点理解在返回值取决于它时更改 self 是不安全的,但是,我不明白为什么 self.v 的不可变借用在下一次迭代中仍然存在,当我们尝试调用时再次b()

【问题讨论】:

  • 对我来说看起来像是一个编译器错误。我认为第二个版本也不应该编译(事实上,如果你删除 else 分支,它不会编译)
  • @PaoloFalabella,这不是错误。编译器可以推断,当self.v 的第二次借用处于活动状态时,不会调用self.b(),因为在使用此借用后循环无条件结束。如果你删除 else 分支,那么它就不再是真的了。
  • @red75prim 啊,你是对的,谢谢!

标签: rust borrow-checker


【解决方案1】:

现在“Rustc 无法“处理”有条件的借款回报”。看到这个comment from Gankro on issue 21906

如果只有一个执行路径终止循环,则无法为借用分配正确的生命周期。

我可以建议这种解决方法,但我不确定它是否最佳:

fn c(i: &[u8]) -> Option<(usize, usize)> {
    Some((0, i.len()))
}

fn a(&mut self) -> &[u8] {
    let parse_result;
    loop {
        self.b();

        match Test::c(&self.v) {
            Some(r) => {
                parse_result = r;
                break;
            }
            _ => {}
        }
    }
    let (start, end) = parse_result;
    &self.v[start..end]
}

您可以使用数组索引构造解析结果并将它们转换为循环外的引用。

另一种选择是使用unsafe 来解耦生命周期。我不是安全使用unsafe的专家,所以要注意别人的cmets。

fn a(&mut self) -> &[u8] {
    loop {
        self.b();

        match Test::c(&self.v) {
            Some(r) => return unsafe{ 
                // should be safe. It decouples lifetime of 
                // &self.v and lifetime of returned value,
                // while lifetime of returned value still
                // cannot outlive self
                ::std::slice::from_raw_parts(r.as_ptr(), r.len())
            },
            _ => continue,
        }
    }
}

【讨论】:

  • 这是一个很好的解决方法,但是,在我的例子中,解析器是用nom 编写的(它总是返回切片),所以返回长度不起作用......
  • @Dndx,我在答案中添加了另一个选项。
  • 谢谢,上述使用from_raw_parts的解决方案效果很好。
猜你喜欢
  • 1970-01-01
  • 2021-05-31
  • 2019-05-22
  • 2018-07-28
  • 2013-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多