Argmin argmax skipnan by phungleson · Pull Request #33 · rust-ndarray/ndarray-stats
Does this mean?
indexed_fold_skipnanhas a lifetime'athat is the lifetime of&selfand&A::NotNaninFnMutshould also lives at least'aperiod
Yes. 'a is a lifetime parameter to indexed_fold_skipnan. self is borrowed for lifetime 'a. The element type A must live at least as long as 'a. F takes references to A::NotNan that are valid for lifetime 'a.
why does
Aneeds to live at least'a, is it because of&A::NotNan?
Yes, that is one of the reasons, but somewhat indirectly.
First, it's helpful to understand what exactly it means for a type to live at least as long as a lifetime. Most types, such as f64, Vec<i32>, String, etc., live for the 'static lifetime (so they meet an A: 'a bound regardless of what 'a is) because they don't contain any references. Types have a shorter lifetime when they contain references. For example, &'b i32 lives for lifetime 'b, and Cow<'c, [f32]> lives for lifetime 'c. If A is &'b i32, it meets an A: 'a bound if 'b is at least as long as 'a (i.e. 'b: 'a). See this section of the book.
It's also worth pointing out that A: 'a will always be true in practice in the implementation of MaybeNanExt for ArrayBase<S, D> because A is the element type of the array, and we're taking a reference &'a self to the array (so the elements must live at least as long as 'a).
If we remove the A: 'a bound, the compiler says:
error[E0309]: the associated type `<A as maybe_nan::MaybeNan>::NotNan` may not live long enough
--> src/maybe_nan/mod.rs:248:5
|
248 | / fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B
249 | | where
250 | | F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B;
| |______________________________________________________^
|
= help: consider adding an explicit lifetime bound `<A as maybe_nan::MaybeNan>::NotNan: 'a`...
note: ...so that the reference type `&'a <A as maybe_nan::MaybeNan>::NotNan` does not outlive the data it points at
--> src/maybe_nan/mod.rs:248:5
|
248 | / fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B
249 | | where
250 | | F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B;
| |______________________________________________________^
error[E0309]: the associated type `<A as maybe_nan::MaybeNan>::NotNan` may not live long enough
--> src/maybe_nan/mod.rs:313:5
|
313 | / fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, mut f: F) -> B
314 | | where
315 | | F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B,
316 | | {
... |
323 | | })
324 | | }
| |_____^
|
= help: consider adding an explicit lifetime bound `<A as maybe_nan::MaybeNan>::NotNan: 'a`...
note: ...so that the reference type `&'a <A as maybe_nan::MaybeNan>::NotNan` does not outlive the data it points at
--> src/maybe_nan/mod.rs:313:5
|
313 | / fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, mut f: F) -> B
314 | | where
315 | | F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B,
316 | | {
... |
323 | | })
324 | | }
| |_____^
so the problem is that without the A: 'a bound, the compiler can't guarantee that A::NotNan will live long enough. (Note that A::NotNan must live for at least 'a because we're creating a reference of life 'a to a value of type A::NotNan and passing it to the closure (the &'a A::NotNan parameter). The life of a reference must never be longer than the life of the type it's referencing.)
How does adding an A: 'a bound fix this? After all, we still haven't directly put a bound on A::NotNan like the compiler suggested. I think what's happening here is that an A: 'a bound does in fact guarantee that A::NotNan: 'a is also satisfied. If you look at the definition of the MaybeNan trait, you can see that it's not possible to implement the trait such that NotNan will have a shorter lifetime than Self. (Try to come up with such a case; you'll see it's not possible because any lifetime parameter of the NotNan type must also be a lifetime parameter of Self.¹) So, we're indirectly guaranteeing A::NotNan: 'a by specifying an A: 'a bound.
Fwiw, this is getting pretty into-the-weeds of lifetimes. I didn't think about this when I was writing the function. If we just follow the compiler's suggestions, starting from no bounds:
fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B where F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B;
the compiler says that A::NotNan may not live long enough and suggests adding an A::NotNan: 'a bound:
fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B where A::NotNan: 'a, F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B;
then the compiler says that A may not live long enough (this is the other reason why we need the A: 'a bound; the function signature may not require it, but the implementation does), and the compiler suggests adding an A: 'a bound:
fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B where A: 'a, A::NotNan: 'a, F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B;
This then satisfies the compiler, and we could stop here. We could try removing the A::NotNan: 'a bound to see if the compiler is still satisfied; it turns out that this works. I can justify in retrospect why it did, but I didn't need to think through the details when I was writing the code; I could just follow the compiler's suggestions, and if I tried removing a bound that I needed, the compiler would let me know.
what happens if
Adoesn't live at least'a?
I can't think of a situation where A: 'a wouldn't be satisfied because we're taking &'a self, as explained earlier. If it was possible to create such a case, then if A didn't live at least 'a, we'd be creating references with a lifetime that would outlive their data (e.g. the data might contain references to already-freed memory or something). That's why the compiler requires that A: 'a.
On another note, it would be possible to define indexed_fold_skipnan this way:
fn indexed_fold_skipnan<F, B>(&self, init: B, f: F) -> B where F: FnMut(B, (D::Pattern, &A::NotNan)) -> B;
but that's not as convenient for the caller because they get &A::NotNan references that only live for the body of the closure. This would make argmin_skipnan more difficult to implement because we couldn't store a reference to the current minimum between different calls to the closure. This is an example of a higher-ranked trait bound. See the nomicon and the reference for details.
¹ Technical note: It turns out that this observation (associated types can never have a shorter lifetime than Self) is true for all traits in current Rust. Once Rust has generic associated types, that will no longer be the case for all traits because it will be possible to have traits like trait Foo { type Assoc<'a>; } where the associated type has a lifetime parameter not part of Self. However, it will still be true that the NotNan type in the MaybeNan trait cannot have a shorter lifetime than Self because type NotNan doesn't have any type/lifetime parameters.