rust-ndarray / ndarray

ndarray: an N-dimensional array with array views, multidimensional slicing, and efficient operations
https://docs.rs/ndarray/
Apache License 2.0
3.43k stars 295 forks source link

Lifetime troubles #1341

Closed ViktorWb closed 6 months ago

ViktorWb commented 6 months ago

Hi! I've been stuck on an issue for a while and can't figure out what's causing it. Not sure if there's an issue in ndarray, Rust or if I'm just missing something.

Playground

I'm trying to create a struct containing a list of arrays, where the array elements are string slices:

struct ListOfArrays<'a, D: ndarray::Dimension> {
    data: &'a [ndarray::Array<&'a str, D>],
}

fn does_not_work() {
    let arrays = [ndarray::array![""]];
    let list = ListOfArrays { data: &arrays };
}
// Error: `arrays` does not live long enough
// borrowed value does not live long enough. 
// `arrays` dropped here while still borrowed

Thing is, this same thing is totally doable with other Rust structs, for example if we replace ndarray with Vec:

struct ListOfArraysVec<'a> {
    data: &'a [Vec<&'a str>],
}

fn works() {
    let arrays = [vec![""]];
    let list = ListOfArrays { data: &arrays };
}
// Compiles!

I don't understand why this happens with ndarray, and I haven't been successful in recreating the issue with any struct other than ndarray, so that is why I'm posting here. Any guidance would be much appreciated!

cavenditti-quaternion commented 6 months ago

Hi, a colleague of mine wanted to understand why this happens (we're still learning Rust) so I gave a look.

I belive the problem arises from the drop() of the OwnedRepr of the BaseArray that's called automatically at the end of the scope (see here in data_repr.rs), but I might be misguided here.

Note that this happens also without the list, i.e.:

struct OneArray<'a, D: ndarray::Dimension> {
    data: &'a ndarray::Array<&'a str, D>,
}

fn does_not_work() {
    let onearray = ndarray::array![""];
    let list = OneArray { data: &onearray };
}

So far I didn't manage to find a way to specify lifetimes to make the code compile, other than making the string's lifetime static.

I hope someone else with more knowledge of ndarray's internals (and Rust) can provide a detailed explanation of what's going on 😅

adamreichold commented 6 months ago

I suspect the problem is forcing the lifetime of the array reference and that of the string references to be the same, i.e. I think

struct OneArray<'a, 'b, D: ndarray::Dimension> {
    data: &'a ndarray::Array<&'b str, D>,
}

fn does_work() {
    let onearray = ndarray::array![""];
    let list = OneArray { data: &onearray };
}

should work.

(I think that the difference between using ndarray::Array and Vec comes to the standard library having access to additional unstable language features to tell the compiler that while that Drop impl is running, it is fine with the string references dangling because it will not inspect them during drop, c.f. https://doc.rust-lang.org/nomicon/dropck.html, https://github.com/rust-lang/rust/issues/34761 and https://doc.rust-lang.org/stable/src/alloc/vec/mod.rs.html#395. But note that this is a somewhat advanced topic if you are just starting with Rust.)

ViktorWb commented 6 months ago

Thank you, those link were very helpful. Maybe this can be fixed one day when (and if) those features are stabilized, but for now, I'll probably go for two lifetimes.