Structs and lifetimes, a conundrum

I am using Rust SoapySDR (https://github.com/kevinmehall/rust-soapysdr), but this is not a problem specific to that library binding.

With SoapySDR you open a Device (Device in soapysdr - Rust) and get a struct instance in return. Using the Device a stream can be opened using the rx_stream method. The returned RxStream (RxStream in soapysdr - Rust) instance keeps a back reference to the device it was opened on, so the Device instance must live longer than the RxStream instance – entirely reasonable. There is no problem if you write code in which the Device instance is opened and closed in the same scope. However, I need to open the device and stream in one function and close it in another. This seems like a job for a struct that takes ownership. I have tried:

#[derive(Debug)]
pub struct SoapySDRSrc<'a> {
    frequency: u64,
    device: Option<Device>,
    stream: Option<RxStream<'a, Complex<f32>>>,
    streaming_state: StreamingState,
    cat: gst::DebugCategory,
}

I am sure I should understand what needs to be done to get round this, but having randomly tried lots of stuff, I am going round in ever decreasing circles. Has anyone out there seen this sort of thing and discovered a way out?
but this does not satisfy the compiler. Whenever I try to implement a trait for this struct, I get errors along the lines of:

error[E0478]: lifetime bound not satisfied
   --> src/soapysdrsrc.rs:127:10
    |
127 | impl<'a> ObjectImpl for SoapySDRSrc<'a> {
    |          ^^^^^^^^^^
    |
note: lifetime parameter instantiated with the lifetime 'a as defined on the impl at 127:6
   --> src/soapysdrsrc.rs:127:6
    |
127 | impl<'a> ObjectImpl for SoapySDRSrc<'a> {
    |      ^^
    = note: but lifetime parameter must outlive the static lifetime

You're attempting to create a self-referential struct, which (in short) Rust doesn't allow - if you search that phrase, you'll see a good amount of prior discussion. SoapySDRSrc would own the Device and also own the RxStream, but the latter is borrowing from the device - this leads to SoapySDRSrc borrowing from itself, which is the self referencing aspect.

Your best bet is to rearrange the code to avoid this type of setup - see if you can own the Device separate from the RxStream. You can also look into using a crate like rental, which can facilitate some self-referential use cases (at the cost of ergonomics).

1 Like

The error you asked about is what you get if ObjectImpl requires 'static. That is, it's defined like this (or some variation):

pub trait ObjectImpl: 'static { ... }

You can't fix this error by removing the Device from SoapySDRSrc, even though that would solve the self-borrowing problem, because anything that implements ObjectImpl is forbidden to borrow non-'static data.

rental might still work, depending on what API you need to expose. Note that it's not just ergonomics you give up; you also have to Box the Device so that the RxStream remains valid when the whole struct is moved (e.g., to pass into or return from a function).

Also look into owning_ref, which is another crate that enables self-borrowing structs.

1 Like

Thanks @vitalyd and @trentj. I shall mull a while on this and see what I can do. The important thing is I now have a positive direction.

I think I shall have to come to terms with what:

pub trait ObjectSubclass: ObjectImpl + Sized + 'static {
pub trait ObjectImpl: 'static {
pub trait ElementImpl: ObjectImpl + Send + Sync + 'static {
pub trait BaseSrcImpl: ElementImpl + Send + Sync + 'static {

actually mean and why they are the way they are. They seem to deny a struct with any field that requires an explicit lifetime. I'll take this to the gtk-rs folk.

it turns out that in this case, using gtk-rs and gstreamer-rs, all objects of type SoapySDRSrc and all fields must be on the heap. TIL lots of stuff about Rust, gtk-rs and gstreamer-rs; today is a good day. :slight_smile: