简体   繁体   中英

Implementing a trait method returning a bounded lifetime reference for owned type

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?

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, eg 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.

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, ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM