Rust结构体可以借用“&'a mut self”两次,那么为什么trait不能呢?

bvk5enib  于 2023-01-13  发布在  其他
关注(0)|答案(4)|浏览(183)

以下 rust eclipse 代码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和几个类似的库共享,但是迄今为止实现它的每一种尝试都遇到了上述问题。

osh3o9ms

osh3o9ms1#

以下是弗朗西斯使用隐式生存期的答案的扩展,但它允许返回值受生存期限制:

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方法中具有泛型类型&mut Self,而在struct中具有类型&mut StructNothing,并且编译器不能保证Self不't包含引用。此解决方法确实解决了代码示例的问题。

w3nuxt5m

w3nuxt5m2#

如果你把生存期参数放在每个方法上,而不是trait本身上,it compiles

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

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

mv1qrgav3#

似乎没有人回答“为什么”,所以我来了。
重点是:* 在trait中,我们调用来自同一trait的方法。然而,在free impl中,我们不调用来自同一impl的方法
什么?我们肯定从同一个impl调用方法吗?
让我们更准确地说:我们从相同的impl调用方法,
但不使用相同的泛型参数 *。
您的免费impl基本上等同于以下内容:

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

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

因为impl的泛型生存期是浮点的,所以可以为每个方法单独选择。编译器不调用<Self<'a>>::nothing(self),而是调用<Self<'some_shorter_lifetime>>::nothing(&mut *self)
另一方面,trait的情况完全不同,我们唯一能确定的是Self: Trait<'b>,我们不能用更短的生存期调用nothing(),* 因为Self可能没有实现更短生存期的Trait *,因此,我们被迫调用<Self as Trait<'a>>::nothing(self),结果是我们在为重叠区域借用。
由此我们可以推断出,如果我们告诉编译器Self在 * 任意 * 生存期内实现Trait,它将工作:

fn twice_nothing(&'a mut self)
where
    Self: for<'b> TraitNothing<'b>,
{
    (&mut *self).nothing();
    (&mut *self).nothing();
}

...除了因为issue #84435而无法编译,所以我不知道这是否会成功:(

z0qdvdin

z0qdvdin4#

这真的令人惊讶吗?
你所做的Assert是&mut self至少持续'a的生存期。
在前一种情况下,&mut self是指向结构体的指针。由于借位完全包含在nothing()中,因此不会发生指针别名。
在后一种情况下,&mut self是一个指针 * 指向一个指针 * 指向trait的一个结构体+一个vtable,你在'a的持续时间内锁定了指向TraitNothing的结构体;即每次执行整个函数。
通过删除'a,您隐式地使用了'static,这意味着impl将永久存在,所以这很好。
如果您想解决这个问题,可以将&'a TraitNothing转换为&'static TraitNothing......但我非常肯定这不是您想要做的。
这就是为什么我们在Rust中需要块作用域('b: { .... })...
Try using dummy lifetimes perhaps?

相关问题