There is one unclear type of confusion with borrow happening or not due to lifetime elision on returned structs. I encounter it regularly and from my experience over a year ago, this can confuse beginners struggling with the borrow checker.
Let me illustrate with an easy example:
let mut v = vec![1,2,3];
let mut c = v.clone(); // OK
let mut d = v.iter(); // Conflict with the next line
drop(v); // d still borrowed
// ... use c and d somehow
The compiler error informs you that there is a borrow going on but since clone
and iter
have an almost identical signature (inside impl Vec<T>
) in the sources as well as in the docs:
fn iter(&self) -> Iter<T>
fn clone(&self) -> Vec<T>
it looks a bit like magic. That is until you notice that struct Iter
has a lifetime parameter that makes the expanded signature
fn<'a> iter(&'a self) -> Iter<'a, T>
With the lifetimes, the borrow is clear but without it, Iter
looks like a standalone struct.
In order to notice that, you need to look at the source or docs of struct Iter
(and a beginner would need to know what to look for).
This is a common scenario when creating “proxy” structs such as iterators, drains, guards, combinators etc. It may be clear from the context with things like .iter()
but is less clear with less familiar things like .drain()
(which may or may not borrow the original struct, depending on the type). Perhaps you know better examples
Note that elision in cases such as fn first(&[T]) -> Option<&T>
is not problematic IMO because it is clear that &T
has to have a bounded lifetime. It is the elision on Iter<'a, T>
that is the problem. This is also briefly mentioned in the RFC but without much discussion.
I am bringing it up mainly because I would hope this to be easy to significantly improve:
- We can make it a best practice to include lifetimes of returned structs.
- Based on that, add lifetime annotations to such cases in
std
and possibly other common libs (voluntarily). - We can add a lint advocating explicitness (with
allow
to see the impact at first and to be used in participating libs).
My intention is not to forbid this type of elision but rather to make it beginner and library-user friendlier in the most common cases.
I am looking for your view or other solutions (and any discussions I may have missed)!