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 ofself.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)
}
}
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.