A way to work around the drop check in my code?

Hi Rustaceans,

I have the following code that I'm trying to get compiled but doesn't compile:

/// Fictitious library code I do NOT have control over

struct StringRef<'a>(&'a String);

impl<'a> Drop for StringRef<'a> {
    fn drop(&mut self) {}
}

struct StringRefFactory {
    seed_string: String,
}

impl StringRefFactory {
    fn create(&self) -> StringRef<'_> {
        StringRef(&self.seed_string)
    }
}

/// Code below is what I have control over

struct FactoryTest<'a> {
    factory: StringRefFactory,
    string_ref: Option<StringRef<'a>>,
}

impl<'a> FactoryTest<'a> {
    fn new() -> Self {
        Self {
            factory: StringRefFactory {
                seed_string: String::from("Hello, World"),
            },
            string_ref: None,
        }
    }

    fn set(&'a mut self) {
        self.string_ref = Some(self.factory.create());
    }
}

fn main() {
    let mut factory_test = FactoryTest::new();
    factory_test.set();
}

(Rust Playground)

The compile error I'm getting is

error[E0597]: `factory_test` does not live long enough
  --> src/main.rs:43:5
   |
43 |     factory_test.set();
   |     ^^^^^^^^^^^^ borrowed value does not live long enough
44 | }
   | -
   | |
   | `factory_test` dropped here while still borrowed
   | borrow might be used here, when `factory_test` is dropped and runs the destructor for type `FactoryTest<'_>`

I'd like to have a utility struct that holds on to StringRefFactory and StringRef, which is what I call here FactoryTest. However, I think Rust is rightfully rejecting this code because of the drop check for FactoryTest?

My questions is given this setting, is it possible to have a struct like FactoryTest and make it compile (preferably within the realm of safe Rust) without getting yelled at by the drop check?

Looks like your type FactoryTest is a self-referencing struct. Either avoid the pattern, or use Rcs or use a crate like ouroboro or self_cell to define it.

Edit: The approach of using Rc is probably not an option if you don't control StringRef.

2 Likes

E.g. with ouroboros

/// Fictitious library code I do NOT have control over

struct StringRef<'a>(&'a String);

impl<'a> Drop for StringRef<'a> {
    fn drop(&mut self) {}
}

struct StringRefFactory {
    seed_string: String,
}

impl StringRefFactory {
    fn create(&self) -> StringRef<'_> {
        StringRef(&self.seed_string)
    }
}

/// Code below is what I have control over
use ouroboros::self_referencing;

#[self_referencing]
struct FactoryTestInternal {
    factory: StringRefFactory,
    #[borrows(factory)]
    #[covariant]
    string_ref: Option<StringRef<'this>>,
}

struct FactoryTest(FactoryTestInternal);

impl FactoryTest {
    fn new() -> Self {
        Self(
            FactoryTestInternalBuilder {
                factory: StringRefFactory {
                    seed_string: String::from("Hello, World"),
                },
                string_ref_builder: |_| None,
            }
            .build(),
        )
    }

    fn set(&mut self) {
        self.0.with_mut(|this| {
            *this.string_ref = Some(this.factory.create());
        });
    }
}

fn main() {
    let mut factory_test = FactoryTest::new();
    factory_test.set();
}

Thank you @steffahn for your prompt reply! Now that you mentioned it, FactoryTest looks to me like a self-referencing struct in disguise, which I did not realize before.

Thanks also for letting me know the crates that come in handy in this type of situation. I'll give them a shot. Appreciate your help.

Note that it’s often not necessary to put two values – one referencing the other – together in the same struct. When it is needed or wanted for some reason, crates offering a safe abstraction for self-referencing structs can be helpful; but if it turns our you don’t need them together in the same struct and it’s okay to just always handle them as two separate objects, one referencing the other, than that’s the easiest approach.


The fact that you did the &'a mut self was able to hide the issue / push it down the road a bit. Note that that API was going to be unworkable anyways, even if the dropck issue could’ve been fixed: A method taking self: &'a mut FactoryTest<'a> would be borrowing the FactoryTest value for its entire existence – you couldn’t do anything else with it anymore afterwards. It’s generally a clear anti-pattern1 in Rust to use &'a mut TYPE where TYPE itself uses the same lifetime 'a a second time. More generally: don’t re-use lifetimes for different things if you don’t have to; although this particular case might have grown out of a compiler error and/or suggestion that originated in the attempt to create a self-referencing struct.

1i.e. a pattern worth avoiding

Indeed, I find not putting StringRefFactory and StringRef together in one struct in this case leads to better & simpler design, as demonstrated below:

/// Fictitious library code I do NOT have control over (same as before)

struct StringRef<'a>(&'a String);

impl<'a> Drop for StringRef<'a> {
    fn drop(&mut self) {}
}

struct StringRefFactory {
    seed_string: String,
}

impl StringRefFactory {
    fn create(&self) -> StringRef<'_> {
        StringRef(&self.seed_string)
    }
}

/// Code below is what I have control over

// updated to contain just `StringRef`
struct FactoryTest<'a> {
    _string_ref: StringRef<'a>,
}

impl<'a> FactoryTest<'a> {
    fn new(factory: &'a StringRefFactory) -> Self {
        Self {
            _string_ref: factory.create(),
        }
    }
}

fn main() {
    // `StringRefFactory` now lives outside `FactoryTest`.
    let factory = StringRefFactory {
        seed_string: String::from("Hello, World"),
    };
    // Only a reference to `factory` is fed into `FactoryTest`.
    let _factory_test = FactoryTest::new(&factory);
}

Thanks again for your helpful feedback.

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.