0
votes

Let's assume I have this struct and this trait:

#[derive(Debug)]
pub struct New<T>(T);

pub trait AsRefNew<'a> {
    fn as_ref(&self) -> New<&'a str>;
}

That is, the AsRefNew trait allows to return a reference with a given lifetime 'a wrapped in a New newtype. This lifetime 'a may be different (and will be) from the lifetime of the &self parameter.

Now I can implement this trait for a New(&str), and make it so that the lifetime of the output is the lifetime of the wrapped &str:

impl<'a> AsRefNew<'a> for New<&'a str> {
    fn as_ref(&self) -> New<&'a str>{
        New(self.0)
    }
}

My problem is that I would like to implement the trait for New(String), and this time, I would like 'a to actually match the lifetime of self. My understanding is that something like that should work:

impl<'a> AsRefNew<'a> for New<String> where Self: 'a{
    fn as_ref(&self) -> New<&'a str> {
        New(self.0.as_str())
    }
}

Except it does not:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:16:20
   |
16 |         New(self.0.as_str())
   |                    ^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 15:5...
  --> src/main.rs:15:5
   |
15 |     fn as_ref(&self) -> New<&'a str> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:16:13
   |
16 |         New(self.0.as_str())
   |             ^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 14:6...
  --> src/main.rs:14:6
   |
14 | impl<'a> AsRefNew<'a> for New<String> where Self: 'a{
   |      ^^
note: ...so that the expression is assignable
  --> src/main.rs:16:9
   |
16 |         New(self.0.as_str())
   |         ^^^^^^^^^^^^^^^^^^^^
   = note: expected `New<&'a str>`
              found `New<&str>`

I tried different variations of lifetimes and generic, but I can not find a better way to express the fact that I, in this case, want 'a to match '_.

The goal is to have this snippet to work:

fn main() {
    // This works:
    let a = String::from("Hey");
    let b;
    {
        let c = New(a.as_str());
        b = c.as_ref().0;
    }
    println!("{:?}", b);
    
    // I would like that to work as well:
    let a = String::from("Ho");
    let b;
    let c = New(a);
    {
        b = c.as_ref().0;
    }
    println!("{:?}", b);
}

Any ideas ?

2
I'm not sure if this question is answerable until you elaborate on "This lifetime 'a may be different (and will be) from the lifetime of the &self parameter" with some concrete examples. Please edit your question to update it with additional clarifying details. Given your question as-is I was able to come up with this example but I'm not sure if it actually solves your problem.pretzelhammer
Thanks for having a look ! I added an example to show what I would like to have ! So I really neeed the implementation for New<String>, and not for New<&String>.Alex Péré

2 Answers

1
votes

As explained by Sven, in order to make that work, we would need two different method prototypes, and that is not possible with the AsRefNew trait as it is defined.

Still, it can be modified to make the small snippet work by, e.g. introducing a second lifetime in the signature:

#[derive(Debug)]
pub struct New<T>(T);

pub trait AsRefNew<'b, 'a> {
    fn as_ref(&'b self) -> New<&'a str>;
}

impl<'a> AsRefNew<'_, 'a> for New<&'a str> {
    fn as_ref(&self) -> New<&'a str>{
        New(self.0)
    }
}

impl<'b, 'a> AsRefNew<'b, 'a> for New<String> where 'b:'a {
    fn as_ref(&'b self) -> New<&'a str> {
        New(self.0.as_str())
    }
}

impl<T> New<T> {
    pub fn test<'b, 'a>(&'b self) -> New<&'a str> where Self: AsRefNew<'b, 'a> {
        self.as_ref()
    }
}

The following snippet now works:

fn main() {
    // This works:
    let a = String::from("Hey");
    let b;
    {
        let c = New(a.as_str());
        b = c.as_ref().0;
    }
    println!("{:?}", b);
    
    // It now works
    let a = String::from("Ho");
    let b;
    let c = New(a);
    {
        b = c.as_ref().0;
    }
    println!("{:?}", b);
}

And so is this generic implementation of a method on the New type:

impl<T> New<T> {
    pub fn test<'b, 'a>(&'b self) -> New<&'a str> where Self: AsRefNew<'b, 'a> {
        self.as_ref()
    }
}

The only problem now is that the signature is super ugly! I wonder whether this could made simpler with gats.

0
votes

This isn't really possible. Trait methods can only have a single prototype, and all implementations have to match that prototype.

For the case New<&'a str>, your prototype needs to be what you have

fn as_ref(&self) -> New<&'a str>;

For the case New<String>, on the other hand, you would need

fn as_ref<'b>(&'b self) -> New<&'b str>;

instead, i.e. the lifetime of the return value needs to be tied to the lifetime of the self reference. This prototype is different, since lifetimes are part of the prototype.

Your attempt to solve this with a trait bound Self: 'a can't work. The type Self is New<String> here, which does not contain any references, so it in fact trivially fulfils Self: 'static, and thus any lifetime bound. The bound does not further constrain the Self type in any way. You can't restrict the lifetime of the self reference at the impl level, and bounding it at the function definition level will result in diverging from the prototype in the trait definition, as explained above.

For the same reason, you can't get an &'a str from a Cow<'a, str> by dereferencing. The Cow might be owned, in which case the returned reference can only live as long a the self reference used for dereferencing.