How to design proper lifetimes / proper drop design?

With the project code: GitHub - teohhanhui/callbag-rs at 51f3d98a86f816f10af2809ecbf50704ad574be5

Running this test:

cargo test --test flatten -- --nocapture --test it_should_not_try_to_unsubscribe_from_completed_source_when_for_inner_errors

results in deadlock.

This is because the Nursery's need to be dropped:

Warning : If ever you wait on the stream to finish, remember it will only finish if there are no Nursery ’s alive anymore. You must drop the Nursery before awaiting the NurseryStream . If your program deadlocks, this should be the first place to look.

If I understand correctly, there is a Nursery owned by this closure here:

which is itself owned by:

Since everything has 'static lifetime, this basically means the Nursery in the closure is never dropped, so the .await never completes.

Let's say I want to drop / allow dropping a Source<T> when it receives Message::Error(_) | Message::Terminate, e.g. here:

or here:

(only after doing what needs to be done, such as sending Message::Error(_) | Message::Terminate to the sink).

How would I go about it? (This is not actually possible with 'static lifetime, right?)

I haven't looked at your code in detail, but the usual solution would be to put an Option<...> somewhere in the ownership chain. When that gets set to None through something like Option::take(), any contents it previously held will be dropped.

1 Like

So, something like Message::Handshake(Option<Callbag<O, I>>) instead of Message::Handshake(Callbag<O, I>)?

This makes it less ergonomic... and I'm not sure this API makes sense to the library user?

If it makes no sense to library user then Option goes into your own code. Note that if you do something like:

fn Foo(bar: Bar) {
   let holder = Some(bar);
   …
}

And then later do holder.take() your bar would become “taken” and than, later, dropped (after use). If you would just do holder.take() (without assigning taken object to anything) it would be dropped immediately.

That's why 2e71828 says you need Option “somewhere in the ownership chain”, not necessarily as argument to your function or closure: question of where should you put an Option doesn't have a single answer, but “take from Option” is how you implement what's called “nullable pointer” in other languages (or just a simgle “pointer” because most languages don't have a “non-nullable pointer”).

2 Likes

Hmm... Actually, isn't inner_source dropped after this call?

and source (which is a clone of the same closure) is moved into inner_talkback here:

If so, I'm not sure what is preventing this closure from being dropped:

provided I do:

inner_talkback.store(None);

upon receiving Message::Error(_) | Message::Terminate

How can I debug ownership? (i.e. "Why isn't this being dropped?")

The rules for objects are much simpler than for references. NLL only affects borrowing rules, not ownership.

Drop works similarly to destructor in C++ and is called when object goes out of scope (only if object is not moved somewhere else, of course).

And similarly to C++ the simplest way to see where Drop is called is just to implement it and and println! to it (or set a breakpoint).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.