简体   繁体   中英

How do I implement a trait for a type with "rented" reference

NB: I tried to keep this post as concise as possible, the full code can be found at https://github.com/pchampin/pair_trait .

The problem

I have defined the following trait:

pub trait Pair {
    type Item: Borrow<str>;
    fn first(&self) -> &Self::Item;
    fn second(&self) -> &Self::Item;
}

I have generic implementations of this trait for (T,T) and [T;2] for any T implementing Borrow<str> .

I also have a type, built with the rental crate, containing a String and two Cow<str> that borrow from that string:

#[rental(covariant)]
pub struct SelfSustainedPair {
    line: String,
    pair: (Cow<'line, str>, Cow<'line, str>),
}

I would like this type to implement the Pair trait above, but I can't find a way to do it.

Attempt #0

impl SelfSustainedPair {
    pub fn first(&self) -> &Cow<str> { self.suffix().first() }
    pub fn second(&self) -> &Cow<str> { self.suffix().second() }
}

I know that this is not an implementation of the trait, but I just wanted to be sure that I could implement the methods first and second . The answer is yes: the code above compiles.

Attempt #1

impl Pair for SelfSustainedPair {
    type Item = Cow<str>;
    fn first(&self) -> &Cow<str> { self.suffix().first() }
    fn second(&self) -> &Cow<str> { self.suffix().second() }
}

This fails to compile, with the message "expected lifetime parameter" for the 2nd line.

This is frustrating because it is so close to attempt #0 above.

Attempt #2

impl<'a> Pair for SelfSustainedPair {
    type Item = Cow<'a, str>;
    fn first(&self) -> &Cow<'a, str> { self.suffix().first() }
    fn second(&self) -> &Cow<'a, str> { self.suffix().second() }
}

Here the compiler is complaining about an "unconstrainted lifetime parameter" for the first line ( impl<'a> ).

Attempt #3

I modified my trait Pair so that it expects a lifetime parameter. Surprisingly, this works, even if the liferime parameter is never used in the definition of the trait...

I then wrote:

impl<'a> Pair<'a> for SelfSustainedPair {
    type Item = Cow<'a, str>;
    fn first(&self) -> &Cow<'a, str> { self.suffix().first() }
    fn second(&self) -> &Cow<'a, str> { self.suffix().second() }
}

Now the compiler complains that it "cannot infer an appropriate lifetime for autoref" in both methods...

Anyway, my intuition is that this is not the right path: the lifetime for which the returned Cow can not be specified independantly of that of self ...

Attempt #4

Ideally, this is what I would like to write:

impl Pair for SelfSustainedPair {
    type Item = Cow<'self, str>;
    fn first(&self) -> &Cow<str> { self.suffix().first() }
    fn second(&self) -> &Cow<str> { self.suffix().second() }
}

but obviously, the compiler doesn't know about the self lifetime.

Unfortunately, the fully intended design is currently unachievable. Still, we can adapt attempt #3 to sort-of work.

The idea of returning a &Self::Item is slightly ill-formed, because the associated type Item already represents a borrowed value. It makes more sense to return it directly:

pub trait Pair {
    type Item: Borrow<str>;
    fn first(&self) -> Self::Item;
    fn second(&self) -> Self::Item;
}

But then you would stumble with the incapability of describing this Item as a Cow<'a, str> where 'a is the lifetime of self . Attempt 3 was close to a solution by adding a lifetime parameter to the trait itself, thus making this arbitrary lifetime a higher-ranked argument to the trait.

pub trait Pair<'a> {
    type Item: 'a + Borrow<str>;
    fn first(&'a self) -> Self::Item;
    fn second(&'a self) -> Self::Item;
}

This Pair<'a> now defines a pair of elements bound to 'a , and are not necessarily contained by self . One possible implementation:

impl<'a> Pair<'a> for (String, String) {
    type Item = Cow<'a, str>;

    fn first(&'a self) -> Self::Item {
        Cow::Borrowed(&self.0)
    }
    fn second(&'a self) -> Self::Item {
        Cow::Borrowed(&self.1)
    }
}

Full example in the Rust Playground

This approach comes at the expense of polluting all APIs relying on this trait with higher ranked trait bounds, so that we can implement Pair<'a> for all lifetimes 'a . For example:

fn foo<T>(pair: T)
where
    for<'a> T: Pair<'a>,
{
    unimplemented!()
}

In order to achieve this 'self lifetime for constraining the associated type Item , we need Generic Associated Types (GATs). Once implemented, we'll be able to write something like this:

pub trait Pair {
    type Item<'a>: Borrow<str>;
    fn first(&'a self) -> Self::Item<'a>;
    fn second(&'a self) -> Self::Item<'a>;
}

impl Pair for (String, String) {
    type Item<'a> = Cow<'a, str>;

    fn first(&'a self) -> Self::Item<'a> {
        Cow::Borrowed(&self.0)
    }
    fn second(&'a self) -> Self::Item<'a> {
        Cow::Borrowed(&self.1)
    }
}

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