Give a value to (a constructor-type function that only takes a reference)

Viewed 34

Suppose there is a function

fn basket_from_apple(apple: &'a mut Apple) -> Basket<'a>;

which makes a Basket which refers to Apple somewhere inside of it. I can't modify the code for basket_from_apple. It's in a different package.

I want to be able to just give basket_from_apple the Apple, so I don't need to hold on to it, and I want Basket's Drop to destroy the Apple along with the Basket.

Is there some way of doing that in rust? For instance, is there some kind of box-like object, AppleBasket, that I could somehow put the Apple and the Basket into? I want to keep the Apple private, and I want to keep the Basket unmovable, but still accessible by immutable reference.

1 Answers

You can define your AppleBasket using the ouroboros library, which allows creating “self-referential structs” that contain parts that borrow from other parts. Here is what that looks like:

#[ouroboros::self_referencing]
struct AppleBasket {
    apple: Apple,
    #[borrows(mut apple)]
    #[covariant]
    basket: Basket<'this>,
}
impl AppleBasket {
    fn from_apple(a: Apple) -> AppleBasket {
        AppleBasket::new(a, |a: &mut Apple| basket_from_apple(a))
    }
}
// ...
fn some_func() {
    let a = Apple::new();
    let ab = AppleBasket::from_apple(a);
    // `ab` can be freely moved
    let b = ab.borrow_basket();
}

Sometimes, this is the best, or only, way to solve problems such as you describe. However, ouroboros has some performance costs, risk of unsoundness (if there are any bugs), and a somewhat necessarily-awkward API, so you should see it as the last resort; often there is a way to avoid needing the borrow, and put types together in a different way.

  • The very basic case is that maybe you don't actually need to keep the Basket around, but can write a function that creates it every time it's needed.

  • Sometimes libraries are simply poorly designed — the author did not think of supporting use cases where it would be useful to keep the Basket around, but there are such, and the best solution is to modify the library (and contribute the improvement back if it's welcome). (For example, maybe the Basket can hold a value of a generic type that implements AsMut<Apple>, or a custom Fruit trait).

    (But there can also be reasons, obvious or subtle, why libraries' types must work the way they do.)

  • You can put the Apple and Basket in local variables in a loop running on a thread or async task, which accepts messages instructing it on how to manipulate the Basket. The message channel (in a suitable wrapper type, perhaps) can be passed around freely.

  • If there's only ever one Apple (or a finite number) then you can Box::leak() it so as to get an &'static mut Apple, allowing you to create a Basket<'static>. The disadvantage of this is that it's not (safely) possible to recover the memory used by the Apples, so it should not be done if apples are regularly created and destroyed.

Related