[Solved] How to own a thing as well as a `&mut` to the thing

#1

Hey, I’m in a situation where I’m using something similar to these types from a 3rdparty library I have no control over:

struct InnocentDependency {}

struct MeowIWantAMutRef<'a> {
    inno: &'a mut InnocentDependency,
}

impl<'a> MeowIWantAMutRef<'a> {
    fn new(inno: &'a mut InnocentDependency) -> Self {
        Self { inno }
    }
}

And now I want a struct which owns both an InnocentDependency and a MeowIWantAMutRef.

struct IWannaOwnThings<'a> {
    inno: InnocentDependency,
    meow: MeowIWantAMutRef<'a>,
}

impl<'a> IWannaOwnThings<'a> {
    fn new() -> Self {
        let mut inno = InnocentDependency {};
        Self {
            inno,
            meow: MeowIWantAMutRef::new(&mut inno),
        }
    }
}

Unfortunately, the code above doesn’t work:

...
27 |             meow: MeowIWantAMutRef::new(&mut inno),
   |                   ----------------------^^^^^^^^^-
   |                   |                     |
   |                   |                     borrowed value does not live long enough
   |                   argument requires that `inno` is borrowed for `'a`
28 |         }
29 |     }

Playground link

How can I make this work? I tried but just can’t figure it out. Thanks a lot in advance for any hints or advice :slight_smile:

0 Likes

#2

The problem is that inno, as said in the error, doesn’t live for long enough, meaning that it only lives for as long as the function call.

fn new() -> Self {
    let mut inno = InnocentDependency {};              //<-+ Lifetime starts
    Self {                                             //  |
        inno,                                          //  |  This is `inno`'s lifetime
        meow: MeowIWantAMutRef::new(&mut inno),        //  |
    }                                                  //  |
}                                                      //<-+ Dropped here

and you’d end up with a dangling pointer. To fix this, I’d suggest having to pass in an InnocentDependency into IWannaOwnThings::new.


Another problem is self-referential structs where meow keeps a reference to inno and therefore refers to a member of the same struct. This is again fixed by following the fix I posted above. Look at this for more info

0 Likes

#3

Why do you need a struct containing a value and a mut reference to the same value in the first place?

3 Likes

#4

Hey @OptimisticPeach and @Aloso, thanks a lot for your answers.

and you’d end up with a dangling pointer

Well, I move inno into my struct, so it should be valid for as long as my struct lives, or am I wrong?

Why do you need a struct containing a value and a mut reference to the same value in the first place?

I basically need something to own the InnocentDependency and in my case I think it needs to be the same struct:
I’m using these Godot bindings to create a struct which behaves like a Godot class. To my knowledge I have not the option to pass additional parameters into the struct, I can only define a “constructor”, see the godot_class! macro. Please correct me if I’m wrong!

Therefore, are there ways to do this or other options I could try? Thanks a lot!

0 Likes

#5

Let me make an adjustment to that:

Well, I move inno into my structure, so it [the reference created on construction of my struct] should be valid for as long as inno remains in the same scope.

Let’s say that when you first move inno into the struct you then take a reference to the new struct’s inno, and set it. Well then you move the struct out of the current function and into another function. In GC languages this would most likely be moving a pointer and that’s it, but in Rust this moves the entire object (which is good in this case) and when it moves the entire object into the caller’s stack memory it leaves the pointer dangling and therefore invalid. This would happen any time there is a move on the struct, like passing it (but not a reference to it) to a function, returning it, or dropping it.

2 Likes

#6

Thank you very much for the explanation, this wasn’t clear to me. I think I understand the issue now.

Given that, is there a way I can fix my issue?

1 Like

#7

Okay, thanks to the SO question you referenced @OptimisticPeach, I found rental and it let me do it:

#[macro_use]
extern crate rental;

pub struct InnocentDependency {
    pub foo: i32,
}

pub struct MeowIWantAMutRef<'a> {
    inno: &'a mut InnocentDependency,
}

impl<'a> MeowIWantAMutRef<'a> {
    pub fn new(inno: &'a mut InnocentDependency) -> Self {
        Self { inno }
    }

    pub fn mutate_it(&mut self) {
        self.inno.foo += 1;
    }
}

rental! {
    mod rentals {
        use super::*;

        #[rental_mut]
        pub struct IWannaOwnThings {
            inno: Box<InnocentDependency>,
            meow: MeowIWantAMutRef<'inno>,
        }

    }
}

use rentals::*;

fn main() {
    let mut test = IWannaOwnThings::new(
        Box::new(InnocentDependency { foo: 0i32 }),
        |inno| MeowIWantAMutRef::new(inno)
    );

    let mut inno: &mut InnocentDependency = test.ref_rent_mut(
        |meow| { meow.mutate_it(); meow.inno }
    );
    println!("{}", inno.foo);
    inno.foo += 1;
    println!("{}", inno.foo);
}

The resulting code is really ugly though and I don’t like it at all. If I had the choice, like @Aloso pointed out, I definitely wouldn’t create a structure like this. If there is a better solution or I have missed something that makes me having to own the &mut ref in the same struct, I’d be very happy to know!
Going to mark this as solved. Thank you again for your help!

0 Likes

#8

Hmm … Ok, I’m not seeing an option to mark this as solved. I’m new here, how this work? :slight_smile:

0 Likes

#9

For now, you can edit the title to add “[solved]” if you like. (We don’t have the “solved” plugin configured for this forum yet.)

1 Like