为什么我的特质需要一个生命周期参数?

时间:2016-03-20 18:45:29

标签: rust lifetime

作为一个Rust新手,我可能有点天真地开始这个:

...

pub trait Decode<T> {
    fn decode_from<R: io::Read + ?Sized>(&mut self, stream: &mut R) -> T;
}

pub struct MQTTFrame<'a> {
    pub payload: &'a Vec<u8>,
}

pub struct MQTTFrameDecoder<'a> {
    pub payload: &'a mut Vec<u8>,
}

impl<'a> Decode<MQTTFrame<'a>> for MQTTFrameDecoder<'a> {
    fn decode_from<R: io::Read + ?Sized>(&mut self, stream: &mut R) ->    MQTTFrame<'a> {
        stream.read(&mut self.payload);
        MQTTFrame{ payload: self.payload }
    }
}

在尝试编译时,迎接了:

src/testbed/mod.rs:31:24: 31:36 error: cannot infer an appropriate   lifetime for automatic coercion due to conflicting requirements [E0495]
src/testbed/mod.rs:31         MQTTFrame{ payload: self.payload }
                                                  ^~~~~~~~~~~~
src/testbed/mod.rs:29:5: 32:6 help: consider using an explicit lifetime  parameter as shown: fn decode_from<R: io::Read + ?Sized>(&'a mut self,   stream: &mut R)
 -> MQTTFrame<'a>
src/testbed/mod.rs:29     fn decode_from<R: io::Read + ?Sized>(&mut self, stream: &mut R) -> MQTTFrame<'a> {
src/testbed/mod.rs:30         stream.read(&mut self.payload);
src/testbed/mod.rs:31         MQTTFrame{ payload: self.payload }
src/testbed/mod.rs:32     }

StackOverflow上的某个地方 - 抱歉,我忘记了 - 在类似案例的某个人建议添加像这样的生命周期参数(省略未更改的代码):

pub trait Decode<'a, T> {
    fn decode_from<R: io::Read + ?Sized>(&'a mut self, stream: &mut R) -> T;
}

impl<'a> Decode<'a, MQTTFrame<'a>> for MQTTFrameDecoder<'a> {
    fn decode_from<R: io::Read + ?Sized>(&'a mut self, stream: &mut R) -> MQTTFrame<'a> {
        stream.read(&mut self.payload);
        MQTTFrame{ payload: self.payload }
    }
}

瞧瞧!它汇编。现在如果我只能理解为什么它会编译。有人可以解释

  1. 为什么原始代码没有编译?
  2. 为什么修改后的代码会编译?

2 个答案:

答案 0 :(得分:2)

这是一个无法编译的简化测试用例(playpen):

pub trait Decode<T> {
    fn decode_from<'b>(&'b mut self) -> T;
}

pub struct MQTTFrame<'a> {
    pub payload: &'a Vec<u8>,
}

pub struct MQTTFrameDecoder<'a> {
    pub payload: &'a mut Vec<u8>,
}

impl<'a> Decode<MQTTFrame<'a>> for MQTTFrameDecoder<'a> {
    fn decode_from<'b>(&'b mut self) -> MQTTFrame<'a> {
        MQTTFrame{ payload: self.payload }
    }
}

请注意,decode_from函数有elided the lifetimes,并删除了冗余流参数。

很明显,该函数正在使用任意短的生命周期'b的引用,然后将其扩展为具有生命期'a。这是可变引用的一个问题,因为你可以在同一时间可变地和不可变地借用一些东西:

fn main() {
    let mut v = vec![];
    /* lifetime 'a */ {
        let mut decoder = MQTTFrameDecoder{ payload: &mut v };
        let frame: MQTTFrame;
        /* lifetime 'b */ {
            frame = decoder.decode_from(); // borrows decoder just for lifetime 'b
        }
        // v is mutably borrowed (by decoder) and immutably borrowed (by frame) at the same time! oops!
        decoder.payload.push(1);
        println!("{:?}", frame.payload);
    }
}

因此借用检查器拒绝让函数编译。

但是,如果您强制引用decoder有生命周期'a,则不再存在问题。编译器不能使用寿命较短的引用,它必须可以更长时间地使用decoder,因此当我们再次尝试借用它时,编译器应该给我们一个错误。

为了达到这个目的,我们想写一下

fn decode_from(&'a mut self) -> MQTTFrame<'a> {
    MQTTFrame{ payload: self.payload }
}

但现在我们收到错误:

<anon>:14:5: 16:6 error: method `decode_from` has an incompatible type for trait:
 expected bound lifetime parameter 'b,
    found concrete lifetime [E0053]

要解决这个问题,我们需要让我们的特质意识到你只能decode_from某些生命周期,而不是任意生命周期。所以将解码更改为

pub trait Decode<'a, T> {
    fn decode_from(&'a mut self) -> T;
}

并对实施进行适当的更改

impl<'a> Decode<'a, MQTTFrame<'a>> for MQTTFrameDecoder<'a> { ... }

现在,如果我们尝试上面的代码(playpen is.gd/BLStYq),借阅检查器会抱怨:

<anon>:28:9: 28:24 error: cannot borrow `*decoder.payload` as mutable more than once at a time [E0499]
<anon>:28         decoder.payload.push(1);

这是因为,现在,decoder的引用在调用函数'a时必须具有生命周期decode_from。注释掉有问题的行,其余的示例编译!这段代码现在是安全的,因为没有延长可变的生命周期。

除了:

由于对decoder的引用必须与解码器本身一样长,因此在调用decoder后,您实际上根本无法使用decode_from。在这种情况下,最好通过self代替&'a mut self来表达这一点。然后语法更清晰一点,很明显,一旦使用了解码器,就无法再次使用它。

pub trait Decode<T> {
    fn decode_from(self) -> T;
}

pub struct MQTTFrame<'a> {
    pub payload: &'a Vec<u8>,
}

pub struct MQTTFrameDecoder<'a> {
    pub payload: &'a mut Vec<u8>,
}

impl<'a> Decode<MQTTFrame<'a>> for MQTTFrameDecoder<'a> {
    fn decode_from(self) -> MQTTFrame<'a> {
        MQTTFrame{ payload: self.payload }
    }
}

答案 1 :(得分:0)

Lifetime elision仅在非常简单的情况下起作用。这使它们变得脆弱,但很容易解释(简单的情况也很常见)。

只要你有一个通用的生命周期参数,elision就不再适用了 - 编译器拒绝猜测你的意图。