Trait bound to constraint a trait's associated type to not own data

I am not sure that my title is very convincing, let me try to expose the problem that I am facing.

I have a trait, which defines a generic function with a return type to be defined by the implementations.

pub trait Encoder<Value> {
    /// Return type of the encode method
    /// This type has to expose a slice of bytes, but does not have to have ownership
    type EncodedValue<'a>: AsRef<[u8]>
    where
        Value: 'a;

    /// Encode a value
    fn encode(value: &Value) -> Self::EncodedValue<'_>;
}

The EncodedValue only needs to expose a slice of bytes. It can be owned, such as Vec<u8>, but it could also be just a reference to some bytes. The important thing here is that the trait Encoder does not define if its implementation generates owned values or references.

For instance, here's one implementation that does not take ownership of anything.

/// An encoder that simply returns the bytes, without taking ownership of them.
struct PassThroughEncoder;

/// We define this encoder just for types that owns some bytes and can expose it
impl<T> Encoder<T> for PassThroughEncoder 
where 
    T: AsRef<[u8]>,
{
    type EncodedValue<'a> = &'a [u8]
    where
        T: 'a;

    fn encode(value: &T) -> Self::EncodedValue<'_> {
        value.as_ref()
    }
}

Then, I can use this implementation, for instance like this (I know it's stupid, i am just trying to create a minimal example)

/// This works fine !
/// But notice that the encoder used is specified to be `PassThroughEncoder`
fn encode_with_specific_encoder<T>(value: &T) -> &[u8] 
where
    T: AsRef<[u8]>,
{
    let encoded = PassThroughEncoder::encode(value);
    encoded.as_ref()
}

But my problem comes when I want to define the function above, but with a generic type for the encoder, ie using the trait and not one implementation.

/// If we leave it to be a generic encoder, then it will not work.
/// The reason why this does not work is because `Encoded` is not necessarly a borrowed type.
/// It could be an owned type with a lifetime definition.
fn encode_with_generic_encoder<'a, T, E>(value: &'a T) -> &[u8]
where
    T: AsRef<[u8]>,
    E: Encoder<T>,
    // BELOW are all the trait bounds that I tried to add, and none of them works...
    // E::EncodedValue<'a>: 'a,
    // <E as Encoder<T>>::EncodedValue<'a>: 'a
    // E::EncodedValue<'a>: Borrow<[u8]> 
{
    let encoded = E::encode(value);
    encoded.as_ref()
}

Of course, I understand that this function can't work if the output of E::encode is an owned type.

Hence my question: How can I restrict the implementation of encode_with_generic_encoder to only work with implementations of Encoder where the returned type EncodedValue is not owned but is a borrow ?

I have tried the 3 commented lines in the trait bounds, but none of them works. Is this simply not possible ?

Thanks in advance for any help !

I don't think either AsRef or Borrow can work here because of their signatures. Neither allows us to specify a lifetime for the returned reference, taking the elided one from &self as the lifetime for the returned reference instead. So—to satisfy the return type of encode_with_generic_encoder—we must make sure that whatever encoded is, we must be able to retrieve a reference to a byte slice from it that is alive for 'a, which we can't express with either trait.

The options for you to chose from as far as I see it are (I) either make sure that EncodedValue is &'a [u8] to begin with:

fn encode_with_generic_encoder<'a, T, E>(value: &'a T) -> &'a [u8]
where
    E: Encoder<T, EncodedValue<'a> = &'a [u8]>,
{
    let encoded = E::encode(value);
    encoded
}

Playground.

or (II) define your own AsRef trait that allows specifying the lifetime of the reference returned from as_ref:

pub trait MyAsRef<'a, T>
where
    T: ?Sized,
{
    // Required method
    fn my_as_ref(&self) -> &'a T;
}

impl<'a> MyAsRef<'a, [u8]> for &'a [u8] {
    fn my_as_ref(&self) -> &'a [u8] {
        self
    }
}

fn encode_with_generic_encoder<'a, T, E>(value: &'a T) -> &'a [u8]
where
    E: Encoder<T>,
    E::EncodedValue<'a>: MyAsRef<'a, [u8]>,
{
    let encoded = E::encode(value);
    encoded.my_as_ref()
}

Playground.

3 Likes

There's an existing conversion trait that can express this:

E::EncodedValue<'a>: Into<&'a [u8]>,

(A new trait may still make sense of foreign types you wish had the implementation do not have it.)

3 Likes

A third option would be to change the function's API so that it doesn't need the reference to actually be long-lived:

fn encode_and_process<T, E, O>(value: &T, f: impl FnOnce(&[u8])->O) -> O
where
    T: AsRef<[u8]>,
    E: Encoder<T>,
{
    let encoded = E::encode(value);
    f(encoded.as_ref())
}
2 Likes