NDArrayView lifetimes [solved]

I am trying to establish common trait on top of NDArray and SPRS (sparse array) so my library can be called with both of them.

The goal is to have unified API for 2D array, from which we can extract views to rows (samples) and do many other things like dot products, ...

I hit the wall with lifetime on traits right now for view extraction, simplified example below:

use ndarray::{ArrayView1, Axis, Array2};

pub trait Sample<'a> {
    fn s(&self) -> usize;
}

impl<'a> Sample<'a> for ArrayView1<'a, f64> {
    fn s(&self) -> usize {
        self.len()
    }
}

pub trait WithRows<'a> {
    type Row: Sample<'a>;
    fn row(&'a self, r: usize) -> Self::Row;
}

impl<'a> WithRows<'a> for Array2<f64> {
    type Row = ArrayView1<'a, f64>;

    fn row(&'a self, r: usize) -> ArrayView1<'a, f64> {
        self.subview(Axis(0), r)
    }
}

fn a<'a, T: WithRows<'a>>(x: &'a T) {
    let r = x.row(0);
    r.s();
}

fn b<'a, T: WithRows<'a> + 'a>(x: T) {
    let r = x.row(0);
    r.s();
}

Function a compiles without problem.
Function b produces:

rror[E0597]: `x` does not live long enough
  --> src/data_types.rs:35:13
   |
35 |     let r = x.row(0);
   |             ^ does not live long enough
36 |     r.s();
37 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 34:1...
  --> src/data_types.rs:34:1
   |
34 | / fn b<'a, T: WithRows<'a> + 'a>(x: T) {
35 | |     let r = x.row(0);
36 | |     r.s();
37 | | }
   | |_^

I tried several approaches with using lifetime subtyping, but none of them seem to work.

You can try a formulation like:

fn b<T: for<'a> WithRows<'a>>(x: T) {
    let r = x.row(0);
    r.s();
}

Basically you cannot let caller specify the 'a and then try to borrow x (now owned by the function) for that lifetime inside b().

1 Like

Works like charm, thanks. Is there any documentation on for<'a> syntax?

This is known as a Higher Rank Trait Bound (HRTB). Unfortunately I think the Rust book intentionally decided to leave out this topic. But you can search for this name (and acronym) on the web and get some scattered docs :slight_smile:.

This doesn’t come up often (which is why the book decided to leave it out, I believe) but the most common case is closures: Fn(&T) is actually for<'a> Fn(&'a T). What this means, in practical terms, is the callee, not the caller, picks the lifetime of the borrow/reference. This SO answer has a good explanation.

1 Like