【问题标题】:Rust struct can borrow "&'a mut self" twice, so why can't a trait?Rust struct 可以借用 "&'a mut self" 两次,那为什么 trait 不能呢?
【发布时间】:2014-11-29 07:34:44
【问题描述】:

以下 Rust 代码compiles successfully

struct StructNothing;

impl<'a> StructNothing {
    fn nothing(&'a mut self) -> () {}

    fn twice_nothing(&'a mut self) -> () {
        self.nothing();
        self.nothing();
    }
}

但是,如果我们尝试将其打包到 trait 中,it fails:

pub trait TraitNothing<'a> {
    fn nothing(&'a mut self) -> () {}

    fn twice_nothing(&'a mut self) -> () {
        self.nothing();
        self.nothing();
    }
}

这给了我们:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
 --> src/lib.rs:6:9
  |
1 | pub trait TraitNothing<'a> {
  |                        -- lifetime `'a` defined here
...
5 |         self.nothing();
  |         --------------
  |         |
  |         first mutable borrow occurs here
  |         argument requires that `*self` is borrowed for `'a`
6 |         self.nothing();
  |         ^^^^ second mutable borrow occurs here
  • 为什么允许第一个版本,而禁止第二个版本?
  • 有什么方法可以让编译器相信第二个版本没问题?

背景和动机

rust-csv 这样的库希望支持流式、零拷贝解析,因为它比分配内存快 25 到 50 倍(根据基准测试)。 But Rust's built-in Iterator trait can't be used for this,因为没有办法实现collect()。目标是定义一个StreamingIterator trait,它可以由rust-csv 和几个类似的库共享,但到目前为止,每次尝试实现它都遇到了上述问题。

【问题讨论】:

  • fn nothing(&amp;'a mut self) 更改为 fn nothing(&amp;mut self) 可解决此问题。鉴于你的函数什么都不返回,你真的需要这个生命周期说明符吗?然而,它看起来确实像一个错误。
  • Levans:是的,如果没有生命周期说明符,设计的其余部分就会分崩离析。但是,如果我们能让生命周期说明符起作用,我们就可以构建一个非常好的StreamingIterator 库。这只是让我们摸不着头脑的最小例子。
  • 我相信这可以通过诸如 HRL(更高等级的生命周期)之类的东西来解决,您可以在其中拥有(假设语法)trait StreamingIterator&lt;T&lt;'*&gt;&gt; { fn next&lt;'a&gt;(&amp;'a mut self) -&gt; T&lt;'a&gt;; }。不过,我们现在还没有能力准确地表达这一点。
  • It was pointed out 我误用了术语:上面应该说“HKL(更高种类的生命周期)”。)

标签: iterator rust lifetime


【解决方案1】:

以下是 Francis 使用隐式生命周期的答案的扩展,但它允许返回值受生命周期限制:

pub trait TraitNothing<'a> {
    fn change_it(&mut self);

    fn nothing(&mut self) -> &Self {
        self.change_it();
        self
    }

    fn bounded_nothing(&'a mut self) -> &'a Self {
        self.nothing()
    }

    fn twice_nothing(&'a mut self) -> &'a Self {
        // uncomment to show old fail
        // self.bounded_nothing();
        // self.bounded_nothing()
        self.nothing();
        self.nothing()
    }
}

它并不完美,但您可以在其他方法中多次调用具有隐式生命周期 change_itnothing 的方法。我不知道这是否能解决你真正的问题,因为最终 self 在 trait 方法中具有泛型类型 &amp;mut Self 而在结构中它具有类型 &amp;mut StructNothing 并且编译器不能保证 Self 不会'不包含参考。此解决方法确实解决了代码示例。

【讨论】:

    【解决方案2】:

    如果您将生命周期参数放在每个方法上而不是特性本身上,it compiles:

    pub trait TraitNothing {
        fn nothing<'a>(&'a mut self) -> () {}
    
        fn twice_nothing<'a>(&'a mut self) -> () {
            self.nothing();
            self.nothing();
        }
    }
    

    【讨论】:

    • 不幸的是,这不允许返回值受'a 的限制,这是@emk 试图解决的真正问题所必需的。
    • 我知道有一个陷阱。但是,究竟为什么呢?另外,我只是尝试将&lt;'a&gt; 放在特征上,将&lt;'b: 'a&gt; 放在方法上,但我只是回到了最初的问题。特征上的&lt;'a&gt; 究竟是什么意思?
    【解决方案3】:

    这真的很令人惊讶吗?

    您所做的断言是 &amp;mut self 至少会持续一生 'a

    在前一种情况下,&amp;mut self 是一个指向结构的指针。不会发生指针别名,因为借用完全包含在 nothing() 中。

    在后一种情况下,&amp;mut self 是一个指针到一个指针到一个结构+一个特征的vtable。您将在'a 的持续时间内锁定实现TraitNothing 的指向结构;即每次的整个功能。

    通过删除'a,您隐含地使用'static,这表示impl 永远存在,所以没问题。

    如果您想解决它,请将&amp;'a TraitNothing 转换为&amp;'static TraitNothing...但我很确定这不是您想要做的。

    这就是我们在 Rust 中需要块作用域 ('b: { .... }) 的原因...

    Try using dummy lifetimes perhaps?

    【讨论】:

    • 特征声明中的&amp;mut self 不是特征对象;它只是一个简单的指针,指向实现该特征的任何值。 (即使它是一个&amp;mut TraitNothing trait 对象,它也只是一个指向结构的指针 + 一个指向 vtable 的指针,没有额外的间接层。)
    • @dbaupp 哦,真的吗?我认为 trait &self 实现是通过类似is.gd/Gl20OQ
    • @Doug,不,他们不是;方法的默认实现与在每个 impl 中编写代码相同,包括静态调度。
    猜你喜欢
    • 1970-01-01
    • 2017-08-10
    • 1970-01-01
    • 2020-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多