A function works, the same function in impl block gives lifetime error

Hi folks,

Could you please take a look what's wrong here?

fn get_slice(s: &str, size: usize) -> Option<&str> {
    s.get(..size)
}

pub trait Utils {
    fn get_slice_b(&self, size: usize) -> Option<&str>;
}
impl<T> Utils for T
where
    T: AsRef<str>,
{
    fn get_slice_b(&self, size: usize) -> Option<&str> {
        self.as_ref().get(..size)
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct Container<'a> {
    data: &'a str,
}
impl<'a> Container<'a> {
    fn slice_it(self, size: usize) -> Option<Container<'a>> {
        // THIS IS OK
        get_slice(self.data, size).map(|data| Container { data })
    }
    fn slice_it_b(self, size: usize) -> Option<Container<'a>> {
        // THIS IS ERR
        self.data.get_slice_b(size).map(|data| Container { data })
    }
}

fn main() {
    let x = Container {
        data: "abcdefghijklmnopqrstuvwxyz",
    };
    let y = x.slice_it(15);

    println!("{:?}", y)
}

When you call self.as_ref() in Utils::get_slice_b(), the compiler is selecting Self = T = &'a str. The result is that the lifetime you get from as_ref(), and also get_slice_b(), is the lifetime of a borrow of self.data in Container::slice_it_b() — a borrow of a reference, not the original reference itself, which therefore can only live as long as the reference (that is owned by Container) exists.

A solution is to generalize the Utils trait so it can match T = str, so you borrow the original string instead of a reference to it:

impl<T: ?Sized> Utils for T
//      ^^^^^^------------------- add this!
where
    T: AsRef<str>,
{
    fn get_slice_b(&self, size: usize) -> Option<&str> {
        self.as_ref().get(..size)
    }
}

That said, I would personally recommend not creating using a helper trait like this, because it will also give you the wrong lifetime if you try to do the same thing with an &&str (e.g. gotten from some .iter() or .filter()). It’s much more robust to have a free function which specifically accepts &str, because then it’s clear which lifetime is to be used — the one for which the str is borrowed. Or, if you must have it, implement it only for str and String and other types that are not themselves borrowing:

/// this is the impl your code calls
impl Utils for str {
    fn get_slice_b(&self, size: usize) -> Option<&str> {
        self.get(..size)
    }
}
/// this is another impl you can have without causing problems
impl Utils for String {
    fn get_slice_b(&self, size: usize) -> Option<&str> {
        self.get(..size)
    }
}
2 Likes

Good point!

Thank you very much, that fixed that!

It seems to me that there are no good reasons/use cases to implement something for T: AsRef<str>. In other words it makes sense to always use T: AsRef<str> + ?Sized.

I would make this more general: regardless of the bounds, any time you write

impl<T: ...> SomeTrait for T {...}

or even more so,

impl<T: ...> SomeTrait for &T {...}

you should usually have T: ?Sized unless there is a reason that can’t work. Besides str in particular, this is also important for allowing your implementations to work with trait objects. For example, a delegating impl for pointers should have T: ?Sized so that it can delegate to trait objects:

impl<T: ?Sized + SomeTrait> SomeTrait for &T {...}
impl<T: ?Sized + SomeTrait> SomeTrait for Box<T> {...}

These impls allows &dyn SomeTrait and Box<dyn SomeTrait> to implement SomeTrait, not just dereference to an implementor, which means that Box<dyn SomeTrait> can be passed to any function that demands a SomeTrait implementor passed by value.

2 Likes