首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何说服借用检查器不再使用借用的引用

如何说服借用检查器不再使用借用的引用
EN

Stack Overflow用户
提问于 2020-01-27 06:11:40
回答 1查看 95关注 0票数 0

我正在寻找一种方法,如何说服借入检查器,以下代码是安全的。

情况

SliceHolder是一个结构,我不能改变它的接口(不同的库)。我想在SliceHolder中临时设置切片,调用一些方法来处理数据,然后完成借用(停止借用切片)。我的问题是,借用检查器不允许我这么做。如果我添加到接口foowhere 'a: 'b借用检查器将在self.slice = slice;上失败,因为输入切片不能被存储(即使是临时的)到结构将更长的生命周期(至少我认为这是原因)。如果我更改为where 'b: 'a借用检查器,x[0] = 5将失败,因为它认为,片被借用时间长于SliceHolder的生命周期。

我只是想临时使用切片的引用,并在函数foo结束时以某种方式释放引用,这样借用检查器就不会将引用视为已用引用。为此,我使用了self.slice = &DEFAULT;,它应该会说服借用检查器,slice不再存储在SliceHolder中,但这不起作用。

我已经找到了不安全和原始指针(self.slice = unsafe {&*(slice as *const [u8])};)的解决方案,但我认为这不是解决这个问题的正确方法。

代码

代码语言:javascript
复制
struct SliceHolder<'a> {
    slice: &'a [u8],
}

const DEFAULT: [u8; 0] = [];

impl<'a> SliceHolder<'a> {
    fn foo<'b>(&mut self, slice: &'b [u8]) -> Vec<u8> {
        self.slice = slice; // FIRST POSITION WHERE BORROW CHECKER COMPLAINS
        let result = do_something_with_holder(&self);
        self.slice = &DEFAULT;
        result
    }
}

// blackbox function, do some computation based on the slice
fn do_something_with_holder(holder: &SliceHolder) -> Vec<u8> {
    Vec::from(holder.slice)
}


fn main() {
    let mut holder = SliceHolder {
        slice: &DEFAULT,
    };

    let mut x: [u8; 1] = [1];
    holder.foo(&x);
    x[0] = 5;
    holder.foo(&x); // SECOND POSITION WHERE BORROW CHECKER COMPLAINS
}

已尝试使用rust 1.40。

EN

回答 1

Stack Overflow用户

发布于 2020-01-27 08:17:05

在尝试解决这样的生命周期问题时,用图表表示所有涉及的生命周期是很有帮助的。有2个命名生命周期,但至少有5个相关生命周期。这只适用于函数SliceHolder::foo

  • 'static.这是由于'static promotion).
  • 'a.,&DEFAULT将具有的生命周期附加到self.
  • 'b.类型的生存期slice.
  • Anonymous生命周期1的生命周期(我称之为'c)。self.
  • Anonymous生命周期2上&mut借用的生命周期(我称之为'd)。与函数体对应的生存期。

这是他们之间关系的图表(请原谅糟糕的ASCII艺术)。图中较高的生命周期通常持续更长时间,但只有在有一系列箭头连接它们的情况下,一个生命周期才会比另一个生命周期长。

代码语言:javascript
复制
        'static
           ^
         /   \
       /       \
     /          \
    |            |
    V            V
   'a           'b
    |            |
    V            |
   'c            |
     \          /
       \      /
         \  /
          V
         'd

添加生命周期界限'b: 'a相当于在此图上添加了一个箭头'a -> 'b,说明'a'b寿命更长,或者具有生命周期'a的借用在整个周期'b中都有效。

让我们来看看这些生命周期在main中是如何交互的。

代码语言:javascript
复制
// holder: SliceHolder<'a>, where 'a: 'static
let mut holder = SliceHolder { slice: &DEFAULT };

let mut x: [u8; 1] = [1];

// Take a mutable borrow of holder (with lifetime 'c1)
// Also take a borrow of x with lifetime 'b1
holder.foo(&x);
// mutate x. This forces 'b1 to end no later than here.
x[0] = 5;
// Take another mutable borrow of holder ('c2)
// and another borrow of x ('b2)
holder.foo(&x);

这表明我们不能使用'b: 'a,因为这意味着'a必须在'b结束之前结束。这将迫使holderx发生突变之前停止存在。但在此之后才使用holder

所以我们已经证明了我们不能有'b: 'a。那还剩下什么呢?当我们执行赋值self.slice = slice;时,我们隐式地从&'b [u8]转换为&'a [u8]。这需要'b: 'a,我们刚刚排除了这一点。self总是必须有类型SliceHolder<'a>,所以我们不能简单地缩短它的生命周期。

抛开

不谈:如果我们不坚持self中的切片始终具有生存期(至少) 'a,我们可能会在某些时候(例如在do_something_with_holder中)感到恐慌,并避免self.slice被重新分配到生命周期更长的东西的控制路径。'b将结束(使slice: &'b [u8]无效),但self将仍然存在并持有无效的引用。如果您曾经使用过unsafe代码,这些是您需要考虑的事情。

然而,我们可以有一个生命周期更短的第二个变量,它的值(即内部切片)与self相同。我们需要第二个变量让类型SliceHolder<'_>的生命周期不长于'a (这样我们就可以使用self的引用),也不能长于'b (这样我们就可以将slice赋值给它的片)。我们在图中看到确实存在一个比'a'b都短的生命周期,即'd

幸运的是,我们不需要担心这一生的命名。重要的是它的存在,编译器会解决剩下的问题。那么我们如何得到第二个变量呢?我们需要以某种方式将self移到一个新变量中。但请记住,我们不能移出可变引用,所以我们必须在内部保留一些有效的引用。

我们已经计划在&DEFAULT中去掉self的引用了,为什么不这样做呢?相关的命令是std::mem::replace,可以像let second_variable = std::mem::replace(self, SliceHolder {slice: &DEFAULT})一样使用。

有一些稍微符合人体工程学的方法可以做到这一点,但这将涉及到为SliceHolder添加一些特性。如果SliceHolder真的只包含一个引用,它可以实现Copy,这使得这种情况下的所有事情都变得更容易。除此之外,为它实现Default (使用&DEFAULT作为默认切片)将允许您使用新稳定的std::mem::take而不是std::mem::replace。这样你就不需要构造内联的默认值了。

可能还有其他一些事情需要考虑,但如果没有一个最小的可重现的例子,就很难说出什么了。我将为您留下一些有效的代码(playground)

代码语言:javascript
复制
struct SliceHolder<'a> {
    slice: &'a [u8],
}

const DEFAULT: [u8; 0] = [];

struct Result;

fn do_something_with_holder(_: &SliceHolder) -> Result {
    Result
}

impl<'a> SliceHolder<'a> {
    fn foo<'b>(&mut self, slice: &'b [u8]) -> Result {
        // new_holder has the exact value that self would have had before
        let mut new_holder: SliceHolder<'_> =
            std::mem::replace(self, SliceHolder { slice: &DEFAULT });

        new_holder.slice = slice;
        let result = do_something_with_holder(&new_holder);
        // self.slice = &DEFAULT; // no longer needed - we've already taken care of that
        result
    }
}

fn main() {
    let mut holder = SliceHolder { slice: &DEFAULT };

    let mut x: [u8; 1] = [1];
    holder.foo(&x);
    x[0] = 5;
    holder.foo(&x);
}
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59923212

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档