Lifetime parameter for `Self` in trait signature

和自甴很熟 提交于 2021-02-10 13:07:36

问题


Consider this simple protocol implementation:

#[derive(PartialEq, Debug)]
enum Req<'a> {
    InputData(&'a [u8]),
    Stop,
}

impl<'a> Req<'a> {
    fn decode(packet: &'a [u8]) -> Result<Req<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Req::InputData(&packet[1..])),
            Some(&0x02) => Ok(Req::Stop),
            _ => Err(format!("invalid request: {:?}", packet)),
        }
    }
}

#[derive(PartialEq, Debug)]
enum Rep<'a> {
    OutputData(&'a [u8]),
    StopAck,
}

impl<'a> Rep<'a> {
    fn decode(packet: &'a [u8]) -> Result<Rep<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Rep::OutputData(&packet[1..])),
            Some(&0x02) => Ok(Rep::StopAck),
            _ => Err(format!("invalid reply: {:?}", packet)),
        }
    }
}

fn assert_req(packet: Vec<u8>, sample: Req) {
    assert_eq!(Req::decode(&packet), Ok(sample));
}

fn assert_rep(packet: Vec<u8>, sample: Rep) {
    assert_eq!(Rep::decode(&packet), Ok(sample));
}

fn main() {
    assert_req(vec![1, 2, 3], Req::InputData(&[2, 3]));
    assert_req(vec![2], Req::Stop);
    assert_rep(vec![1, 2, 3], Rep::OutputData(&[2, 3]));
    assert_rep(vec![2], Rep::StopAck);
}

playground

This works, but the two functions assert_req and assert_rep have identical code with only a difference in types. It is a good idea to write one generic assert_packet:

trait Decode<'a>: Sized {
    fn decode(packet: &'a [u8]) -> Result<Self, String>;
}

#[derive(PartialEq, Debug)]
enum Req<'a> {
    InputData(&'a [u8]),
    Stop,
}

impl<'a> Decode<'a> for Req<'a> {
    fn decode(packet: &'a [u8]) -> Result<Req<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Req::InputData(&packet[1..])),
            Some(&0x02) => Ok(Req::Stop),
            _ => Err(format!("invalid request: {:?}", packet)),
        }
    }
}

#[derive(PartialEq, Debug)]
enum Rep<'a> {
    OutputData(&'a [u8]),
    StopAck,
}

impl<'a> Decode<'a> for Rep<'a> {
    fn decode(packet: &'a [u8]) -> Result<Rep<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Rep::OutputData(&packet[1..])),
            Some(&0x02) => Ok(Rep::StopAck),
            _ => Err(format!("invalid reply: {:?}", packet)),
        }
    }
}

fn assert_packet<'a, T>(packet: Vec<u8>, sample: T)
where
    T: Decode<'a> + PartialEq + std::fmt::Debug,
{
    assert_eq!(T::decode(&packet), Ok(sample));
}

fn main() {
    assert_packet(vec![1, 2, 3], Req::InputData(&[2, 3]));
    assert_packet(vec![2], Req::Stop);
    assert_packet(vec![1, 2, 3], Rep::OutputData(&[2, 3]));
    assert_packet(vec![2], Rep::StopAck);
}

playground

However, this triggers a "does not live long enough" error:

error[E0597]: `packet` does not live long enough
  --> src/main.rs:41:27
   |
41 |     assert_eq!(T::decode(&packet), Ok(sample));
   |                           ^^^^^^ does not live long enough
42 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 37:1...
  --> src/main.rs:37:1
   |
37 | / fn assert_packet<'a, T>(packet: Vec<u8>, sample: T)
38 | | where
39 | |     T: Decode<'a> + PartialEq + std::fmt::Debug,
40 | | {
41 | |     assert_eq!(T::decode(&packet), Ok(sample));
42 | | }
   | |_^

If I understand correctly, the problem is in the function signature:

fn assert_packet<'a, T>(packet: Vec<u8>, sample: T)
where
    T: Decode<'a>

Here, packet is destroyed when the function returns, but the user-provided 'a lifetime parameter says that the lifetime should end somewhere outside the assert_packet function. Is there any right solution? How should the signature look? Maybe higher rank trait bounds could help here?


回答1:


Why does this compile:

fn assert_req(packet: Vec<u8>, sample: Req) {
    assert_eq!(Req::decode(&packet), Ok(sample));
}

while this doesn't?

fn assert_packet<'a, T>(packet: Vec<u8>, sample: T) where T: Decode<'a> + PartialEq + std::fmt::Debug {
    assert_eq!(T::decode(&packet), Ok(sample));
}

The difference is that in the first version, the two textual occurrences of Req name two different instances of the Req<'a> struct, with two different lifetimes. The first occurrence, on the sample parameter, is specialized with a lifetime parameter received by the assert_req function. The second occurrence, used to invoke decode, is specialized with the lifetime of the packet parameter itself (which ceases to exist as soon as the function returns). This means that the two arguments to assert_eq! don't have the same type; yet, it compiles because Req<'a> can be coerced to a Req<'b> where 'b is a shorter lifetime than 'a (Req<'a> is a subtype of Req<'b>).

On the other hand, in the second version, both occurrences of T must represent the exact same type. Lifetime parameters are always assumed to represent lifetimes that are longer than the function call, so it's an error to invoke T::decode with a lifetime that is shorter.

Here's a shorter function that exhibits the same problem:

fn decode_packet<'a, T>(packet: Vec<u8>) where T: Decode<'a> {
    T::decode(&packet);
}

This function fails to compile:

<anon>:38:16: 38:22 error: `packet` does not live long enough
<anon>:38     T::decode(&packet);
                         ^~~~~~

It's possible to use higher rank trait bounds on this function to make it compile:

fn decode_packet<T>(packet: Vec<u8>) where for<'a> T: Decode<'a> {
    T::decode(&packet);
}

However, now we have another problem: we can't invoke this function! If we try to invoke it like this:

fn main() {
    decode_packet(vec![1, 2, 3]);
}

We get this error:

<anon>:55:5: 55:18 error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]
<anon>:55     decode_packet(vec![1, 2, 3]);
              ^~~~~~~~~~~~~

That's because we didn't specify which implementation of Decode we want to use, and there are no parameters the compiler can use to infer this information.

What if we specify an implementation?

fn main() {
    decode_packet::<Req>(vec![1, 2, 3]);
}

We get this error:

<anon>:55:5: 55:25 error: the trait `for<'a> Decode<'a>` is not implemented for the type `Req<'_>` [E0277]
<anon>:55     decode_packet::<Req>(vec![1, 2, 3]);
              ^~~~~~~~~~~~~~~~~~~~

That doesn't work, because the Req we wrote is actually interpreted as Req<'_>, where '_ is a lifetime inferred by the compiler. This doesn't implement Decode for all possible lifetimes, only for one particular lifetime.

In order to be able to implement your desired function, Rust would have to support higher kinded types. It would then be possible to define a type constructor parameter (instead of a type parameter) on the function. For instance, you'd be able to pass Req or Rep as a type constructor requiring a lifetime parameter to produce a specific Req<'a> type.




回答2:


It works correctly if you link the lifetimes involved to the input which you'll be referencing, e.g.

fn assert_packet<'a, T>(packet: &'a [u8], sample: T) where T: Decode<'a> ..

(You'll also need to update the tests to pass borrows rather than owned Vecs.)



来源:https://stackoverflow.com/questions/33481868/lifetime-parameter-for-self-in-trait-signature

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!