An iterator like file_names
will (need to) keep its borrow for the whole duration of the iteration. This is why calling a &mut self
method like .by_name
inside of the loop won’t work. What it also will do is link the borrow given to fn file_names(&self, …)
to the lifetime of the returned file name &str
s, so even after the loop you can only ever pass the name back into by_name
if you were to copy it into an owned String
.
The 0..zip.len()
iterator does not keep any borrow of zip
alive. Even though .len
is also a &self
method, it returns a usize
that has nothing to do with the &self
borrow passed into len
after the method returns. This is why after creating this iterator, and thus during iteration, you can access zip
freely, even by mutable reference, as by_index
requries.
The way the type signatures express this distinction between zip.file_names()
and 0..zip.len()
is a bit tricky, as there’s multiple implicit convenience features at play: lifetime elision on one hand, and special rules of how impl SomeTrait
return types interact with lifetimes on the other hand.
The signature fn len(&self) -> usize
is quite basic. It has simple lifetime elision and stands for a generic signature like fn len<'a>(&'a self) -> usize
. The return type usize
has no lifetimes and is thus considered fully owned and independent of any other borrows or references, in particular the borrow checker does not relate it in any way to the&'a self
reference that was put in.
The signature fn file_names(&self) -> impl Iterator<Item = &str>
stands for, via lifetime elision, the generic signature fn file_names<'a>(&'a self) -> impl Iterator<Item = &'a str>
. Then as for the meaning of impl Iterator…
it stands for some opaque type, let’s call it FileNamesIterator
with certain generic arguments. For the purpose of understanding what the generic arguments are, we must incorporate also the generics of the impl
block in question
impl<R: Read + Seek> ZipArchive<R> {
fn file_names<'a>(&'a self) -> impl Iterator<Item = &'a str> {…}
}
The opaque type will, according to some rules I could look up the RFC for but will not do at the moment, incorporate every generic type parameter present, which is R
, and all lifetime parameters that are mentioned in the trait bound, which does include the only lifetime present, 'a
, in this case. So you can think of the signature as fn file_names<'a>(&'a self) -> FileNamesIterator<'a, R>
. The opaque return type FileNamesIterator<'a, R>
does have a lifetime parameter, and the file_names
method’s function signature relates (i.e. equates) it to the &'a self
reference’s lifetime, which has the effect that as long as the iterator still exits, the original &'a self
reference must be kept alive, and the borrow checker will enforce this. Furthermore, the items of the iterator are of type &'a str
for this same lifetime, so keeping any of these &str
items would also keep the original &'a self
reference of the self.file_names()
call alive, and thus would have the borrow checker still consider self
borrowed while any of those name items are around.