Lifetime in associated type

Hi,

I have a trait DeserializeMessage, which will process Payload and generate some outputs. I want to implement this trait for both owned struct (struct TestOwned) and struct with reference (struct Test<'a>).

#[derive(Debug, Clone)]
pub struct Payload {
    pub metadata: Metadata,
    pub data: Vec<u8>,
}

pub trait DeserializeMessage {
    type Output: Sized;
    fn deserialize_message(payload: &Payload) -> Self::Output;
}

struct Test<'a> {
    data: &'a [u8],
}

impl<'a> DeserializeMessage for Test<'a> {
    type Output = &'a [u8];

    fn deserialize_message(payload: &Payload) -> Self::Output {
        &payload.data
    }
}

struct TestOwned {
    data: Vec<u8>,
}

impl DeserializeMessage for TestOwned {
    type Output = Vec<u8>;

    fn deserialize_message(payload: &Payload) -> Self::Output {
        payload.data.to_vec()
    }
}

The owned version compiles, while the referential version failed to compile. Anyone knows how to return a reference in this trait implementation for struct Test<'a>?

Thanks

Here's the full code

If we unsugar that fn definition (per the rules of lifetime elision), we end up with:

impl<'a> … {
    fn deserialize_message<'payload> (
        payload: &'payload Payload,
    ) -> &'a [u8]
    …
}

Both of these lifetime parameters are chosen by the caller, and what is important is that they can be chosen to be different / completely unrelated.

And yet in the implementation you refer to payload.data, which you can only borrow for 'payload.


What you'd like to write, is that the lifetime on your return type is the one that was used for the payload parameter. This is only possible by changing the trait definition, since it expresses what it is possible to feature with that trait and what not:

trait DeserializeMessage {
    fn deserialize_message<'payload> (
        payload: &'payload Payload,
    ) -> ............
    //   ^^^^^^^^^^^^
    //   what do we put here?
    //    1. we want it to be chosen by the implementor,
    //       so it has to be an associated type.
    //    2. but the `'payload` lifetime is not in scope
    //       for the associated type definition.

The solution to that last point is easy: move the lifetime parameter from the method to the trait:

- trait DeserializeMessage {
-     fn deserialize_message<'payload> (
+ trait DeserializeMessage<'payload> {
+     fn deserialize_message (

This yields:

pub trait DeserializeMessage<'payload> {
    type Output : Sized;
    fn deserialize_message (payload: &'payload Payload)
      -> Self::Output
    ;
}

impl<'payload> DeserializeMessage<'payload>
    for Test<'payload>
{
    type Output = &'payload [u8];

    fn deserialize_message (payload: &'payload Payload)
      -> Self::Output
    {
        &self.data
    }
}

(and for the case where you don't care about the lifetime, such as TestOwned, you can simply impl DeserializeMessage<'_> for TestOwned).


Then, regarding how to depend on this trait in a generic fashion / using this trait, I recommend you read:

It talks about ::serde::Deserialize, but the signature in that trait is equivalent to yours :slightly_smiling_face:

3 Likes

@Yandros

Thank you for the detailed explanation! It works like a magic!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.