Graydon Hoare wrote:
# Lifetime parameters and impls
...
This is *slightly* different than today. This is because, today,
`&self` is equivalent to a self type of `&'b Iterator<'b, T>` rather
than using a fresh lifetime (like `&'a Iterator<'a, T>`). The current
system causes various issues in real-life examples, including this
specific iterator example.
I do not entirely understand what the failure mode is. Are you saying
that today, an omitted lifetime parameter in an impl method signature
gets its default provided from the underlying type's lifetime parameter,
whereas in the new regime it will get a fresh parameter provided?
More or less, yes.
If so, I don't know the exact way it goes wrong (or really see it in the
example here), but it works better, I trust you know why, that's fine.
This is indeed what I mean and yes it can go wrong. Let me spell out
what happens with the Iterator example, it's a bit involved and took me
some time to fully appreciate. To begin, today you have the following
annotations on the `next()` method, effectively:
impl<'b, T> Iterator<'b, T> {
fn next<T>(&'b mut self) -> Option<&'b T> { /* same as before */ }
}
As you can see, both the lifetime of the `&mut self` pointer and the
lifetime of the contents are `'b`. Now let's have an example of some
code that invokes `next()`. This is the standard `find()` function for
vectors, expressed with an iterator. It iterates down the list and
returns the first item that matches some predicate `pred`. I have
included all possible annotations and made all borrows explicit. Key
lines are numbered in comments.
fn find(v: &'lt [T], pred: &fn(&'lt T) -> bool) -> Option<&'lt T> {
let iterator = Iterator(v);
loop {
let result: Option<&'lt T> = /* [1] */
(&'lt mut iterator).next(); /* [2] */
match result {
None => { return None; }
Some(r) => {
if pred(r) {
return Some(r); /* [3] */
} else {
return None;
}
}
}
}
}
The lifetime of the input vector is 'lt. The signature promises to
invoke the predicate function with data in the vector (and hence with
the same lifetime) and to return an element of the vector (and hence
with the same lifetime). The first important point is that, when we
call `next()`, the lifetime of the resulting pointer must also be 'lt
(as indicated in the type annotation [1]). This is because we are going
to be returning this result if it matches the predicate ([3]). Now,
because the lifetime of the pointer to the iterator and the lifetime of
the iteratee are specified as the same ('b), this means that the
lifetime of the borrow must be the same [2].
So, as a result of the above rules, the iterator is borrowed mutably for
the full lifetime 'lt. This makes the borrow checker unhappy because it
is only supposed to allow one mutable alias to iterator at a time, but
the scope of the first mutable borrow outlives the enclosing loop (in
fact, it outlives the entire `find()` function), and thus it seems to
the borrow checker that you are creating new mutable aliases while the
old ones still exist. Thus you get errors. These problems go away when
you don't artificially conflate the lifetime attached to the `&self`
pointer with the lifetime of the stuff it points at.
In general, I prefer this model anyhow because it treats `self` more
like the other parameters to the function, which I think is somewhat
less surprising and well simplify the specification and implementation.
Compatible and future-concern, defer.
Yes, adding label syntax is compatible and not something I particularly
want to nail down now. The main reason that I mention it is that, with
regard to pcwalton's desire for backwards-compatible language support, I
would rather not be locked in to the current labeled loop system we have
now.
regards,
Niko
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev