Is it possible to propagate lifetimes?

Hiho there,

I have a very confusing lifetime error problem in my current project that is probably a result of some wrong specifications of lifetimes in my methods.

fn foo<'a>(&'a self) -> ArrayView1<'a, f32> {
	let l: &'a ContainerLayer = self.get_layers();
	let o: BiasedSignalView<'a> = l.output_signal();
	let a: ArrayView1<'a, f32> = o.data(); // ERROR: 'o' does not live long enough!
	a
}

First let me explain foo:

This is just an example implementation of the real code.
I splitted it up in let-statements to give you more insight in what I actually want to achive and with types it even may be easier to follow the code and its intentions.
Basically I just want to propagate the 'a lifetime through different methods into the resulting ArrayView1<f32>.
This somehow fails and I think this is because the method signatures communicate the propagation of the wrong lifetimes.

Answers to some FAQ:

What is ArrayView1? Link
What is BiasedSignalView? Link

BiasedSignalView is just a thin wrapper around ArrayView1<f32> and ArrayView1<f32> can be understood as a thin wrapper around &[f32].
Both are basically more or less fancy &[f32] slices.

What I want to solve:

With methods like data (Link) I want to transform the borrowed/viewed area of the underlying data and return it with the lifetime associated to the borrowed data, not the lifetime of the pre-transformed object: And as far as I can imagine this is currently my problem.

What I think may be the problem:

So methods like output_signal (Link) or unbias do transform their view into their borrowed data but lifetimes of the returned structures are not explicitly bound to the borrowed data, just the pre-transformed object itself. So when doing ...

foo.output_signal().data()

The Rust compiler correctly reports a bug that the thing returned by output_signal does not live long enough for data to return something with a lifetime associated to the lifetime of foo.

So finally, my question:

How can I change the signature of my functions so that they properly propagate their lifetimes?

Edit

Some of the function signatures used:

The data method:

impl<D, B> BufferBase<D, B>
	where D: ndarray::Data<Elem = f32>,
	      B: marker::Marker
{
	fn data(&self) -> ArrayView1<f32> {
		self.data.view()
	}
}

The output_signal trait and method:

trait HasOutputSignal
{
	fn output_signal(&self) -> BiasedSignalView;
}
impl HasOutputSignal for ContainerLayer {
	fn output_signal(&self) -> BiasedSignalView {
		self.output_layer().output_signal() // Just forwards to other trait implementors
	}

I hope this helps ... :S

Because you declare o inside of the function, after the function ends, o will be deallocated. But you're returning a reference to o, and so, o does not live long enough.

To refer to your title, you cannot use lifetimes to make things live longer, that is, lifetimes are descriptive not prescriptive.

3 Likes

Thank you for your response.

The problem is that o is just a borrowing structure with a lifetime associated to its borrowed content.
So o as well as a are nothing else as fancy wrappers around a basic &[f32] slice.

So demonstrate that:

  • self trivially has self lifetime
  • self.layers too
  • self.get_layers() is just added for clarity and simply returns a ref to self.layers, so same lifetime
  • self.get_layers().output_signal() returns a fancy &[f32] slice with borrowed content from self.layers, so while o has the lifetime of function foo its borrowed data has the lifetime of self.
  • self.get_layers().output_signal().data() same as for o.

What the compiler does: It infers the lifetime of o.
What I want: I want the compiler to infer the lifetime of o's and a's borrowed content (which is just the same as the lifetime of self) since it is the only thing they all really share.

EDIT:

To summarize it: I want to do more or less the same as done in the ndarray crate, for example here. The only difference is that I build my code on top of ndarray and thus have no access to its internals.

I think the problem is that data() returns a reference to ArrayView<'a>, and although ArrayView<'a> itself holds a reference to a slice valid for 'a, you're borrowing the data from a BiasedSignalView; it, however, gets destroyed at function end.

One way to fix this is to make data take self, rather than &self, and consume (detach) the buffer from the view.

Thank you for the summary of the problem, vitalyd.

However, data() does not return a reference to ArrayView<'a> it simply returns an ArrayView<'a> as do all other view-based methods. None of them returns a reference which is why the Rust compiler is getting confused since the real lifetime annotation is stuck within the ArrayView type representation and the same counts for the ArrayView wrapper types.

Following your tip to provide methods taking self instead of &self or &mut self I have successfully implemented a simple into_data() respective to former data() which was quite simple. However, this did not work out (yet) for other functions, such as unbias() (which is substantially important for me) since it does an internal transformation of its ArrayView as can be seen here. The code is currently commented out since I replaced it with a very horrible hack for as long as this issue is not solved.

EDIT

I did some refactoring of my code base following your tip with self argument and so-called into_X methods. This approach, although not solving my initial lifetime problem and being more "ugly" since it requires some boilerplate code and boilerplate API, worked for my current problems.

Link

Sorry, I didn't elaborate properly (I'm on mobile still, so may not elaborate as much as is helpful - let me know and I'll come back to it again :slight_smile: ).

The compiler thinks you're borrowing because of how input lifetimes are associated with output lifetimes. When you call data(&self) on the BiasedSignalView<'a>, the 'a and the elided lifetime of the &self borrow are unrelated. Spelled out without elision it's fn data<'_elided>(&'_elided self) -> ArrayView<'_elided, f32>. If you want to return something with the lifetime of the value itself, you need to specify that in the return type of the method. Otherwise the compiler associates the value with '_elided and that's shorter than 'a here.