One very general suggestion: when you encounter borrowing errors that you don't understand, annotate all the lifetimes explicitly, and give every distinct lifetime parameter a unique name. Say you have something like the following:
struct Keyword<'a> {
buf: &'a str,
}
enum Value<'a> {
Int(i64),
Str(Cow<'a, str>), // string literals may reference the input data without copying I guess
}
impl<'a> Interpreter<'a> {
fn whatever(&mut self, arg: &Keyword) -> Value {
// ...
}
}
If you're getting errors inside or around the call site of whatever
, un-elide all those implicit lifetimes and give meaningful names to all the ones that are the same:
struct Keyword<'interp> {
buf: &'interp str,
}
enum Value<'interp> {
Int(i64),
Str(Cow<'interp, str>), // string literals may reference the input data without copying I guess
}
impl<'interp> Interpreter<'interp> {
fn whatever<'a, 'b, 'c, 'd>(&'a mut self, arg: &'b Keyword<'c>) -> Value<'d> {
// ...
}
}
Use warn(rust_2018_idioms)
to be warned about omitting the parameters from types like Keyword
and Value
.
(Don't forget that dyn Trait
and impl Trait
have implicit lifetimes which are sometimes implicitly 'static
and sometimes something else, depending on context.)
Doing this sometimes gives you more useful error messages, and the exercise of assigning identifiers may give you a hint as to what's wrong (in this example, it seems plausible that 'c
and 'd
should actually be 'interp
, although of course that depends on the desired semantics).
If you've only got one thing you're borrowing all the str
s from, the lifetimes shouldn't get more complex as you nest data structures: they all just borrow from the same place with the same lifetime, regardless of how deep the &
is. That this isn't working for you suggests to me that you're accidentally doing some re-borrowing somewhere instead of copying references. But that's just a guess.