#How to return object1 and derived object2 (having ref to object1) at same time

1 messages · Page 1 of 1 (latest)

night raft
#

I am hitting an issue with "function returning reference to local variables" that I'm having trouble resolving. Here is a simplified version of what I'm trying to do:

fn create_cat_then_populate_then_return_both<'a>() -> (Cat<'a>, Cat<'a>) {
    let mut cat: Cat = Cat::new(None);
    let cat2: Cat = cat.populate();
    (cat, cat2)
}

struct Cat<'a> {
    parent: Option<&'a Cat<'a>>,
    has_children: bool,
}
impl<'a> Cat<'a> {
    fn new(parent: Option<&'a Cat<'a>>) -> Cat<'a> {
        Cat {
            parent,
            has_children: false,
        }
    }
    fn populate(&mut self) -> Cat {
        self.has_children = true;
        Cat::new(Some(self))
    }
}
#

I'm getting an error on the (cat, cat2) return-line saying:

cannot return value referencing local variable cat
returns a value referencing data owned by the current function

#

Basically, I don't understand how to have Rust "return ownership of both cat and cat2 to the caller at the same time", so that I can return cat2 without cat being dropped at the end of the create_cat_then_populate_then_return_both function.

#

In other words, I know cat needs to "stay alive" for me to return cat2, so I figured I could "keep it alive" by returning its ownership to the caller, but even when I add it to the return-value, the error remains.

night raft
#

These were the two key points for me...
First:

A lifetime is a bit of metadata that allows you and the compiler to know how long a value will be valid at its current memory location. That's an important distinction, as it's a common mistake Rust newcomers make. Rust lifetimes are not the time period between when an object is created and when it is destroyed!

#

Second:

After moving parent into the struct, why is the compiler not able to get a new reference to parent and assign it to child in the struct?

While it is theoretically possible to do this, doing so would introduce a large amount of complexity and overhead. Every time that the object is moved, the compiler would need to insert code to "fix up" the reference. This would mean that copying a struct is no longer a very cheap operation that just moves some bits around.

#

Not sure yet what the best alternative is, but at least I know why my original approach doesn't work.

stray matrix
#

yeah this is the self-referential struct problem

#

which is why stuff like Pin exists but it's really unneeded complexity

#

the easiest way to fix this if you are okay with allocation is just using Rc

night raft
#

In my "cat" example, I would put the return-value in a Box then?

fresh plank
#

This is technically the problem owning_ref solves, but it's easier if you don't do it

#

It's generally easier to return cat and then have a separate function that produces cat2 from that

night raft
#

What are the drawbacks of using it?

fresh plank
#

That it's a bit of a pain to use, and that its soundness is currently questionable

#

It depends on whether you can move a box without invalidating pointers to its contents, which is being debated.

night raft
# stray matrix the easiest way to fix this if you are okay with allocation is just using `Rc`

Here's my attempt at using Rc:

struct CatData {
    parent: Option<Cat>,
    has_children: bool,
}
struct Cat {
    data: Rc<CatData>,
}
impl Cat {
    fn new(parent: Option<Cat>) -> Cat {
        Cat {
            data: Rc::new(CatData {
                parent,
                has_children: false,
            })
        }
    }
    fn clone(&self) -> Self {
        Cat { data: self.data.clone() }
    }
    fn populate(&mut self) -> Cat {
        if let Some(data_mut) = Rc::get_mut(&mut self.data) {
            data_mut.has_children = true;
        }
        Cat::new(Some(self.clone()))
    }
}

fn create_cat_then_populate_then_return_both() -> (Cat, Cat) {
    let mut cat = Cat::new(None);
    let cat2 = cat.populate();
    (cat, cat2)
}

fn caller() {
    let (cat1, cat2) = create_cat_then_populate_then_return_both();
    println!("Cat2 has parent:{}", cat2.data.parent.is_some());
}
#

It is compiling!

#

But... it seems kind of longer than it "should be".

#

Did I do something wrong, or is the above roughly the approach you meant when you referred to using Rc?

night raft
#

I finally found out a way to do what I wanted, by having the caller create a simple "data anchor" object, then passing a mutable reference of that into the function; the function then populates that object through the mutable reference, and then also reads from that same reference in the lines below it.

#

Kind of odd that there's not a more idiomatic way to "keep a source object alive as long as the derived object that's returned", but at least it works!

#

On the off-chance of someone finding this thread through search, this is what ended up working for me:

struct DataAnchorFor1<T> {
    val1: Option<T>,
}
impl<T> DataAnchorFor1<T> {
    fn empty() -> Self {
        Self { val1: None }
    }
    fn holding(val1: T) -> Self {
        Self { val1: Some(val1) }
    }
}

async fn get_client<'a>(ctx: &GQLContext<'_>) -> Result<PGClientObject, Error> {
    let pool = ctx.data::<Pool>().unwrap();
    Ok(pool.get().await.unwrap())
}
async fn start_transaction<'a>(anchor: &'a mut DataAnchorFor1<PGClientObject>, ctx: &GQLContext<'_>) -> Result<Transaction<'a>, Error> {
    // get client, then store in anchor object the caller gave us a mut-ref to
    *anchor = DataAnchorFor1::holding(get_client(ctx).await?);
    // now retrieve client from storage-slot we assigned to in the previous line
    let client = anchor.val1.as_mut().unwrap();
    let tx = client.build_transaction().[...etc...].start().await?;
    Ok(tx)
}
async fn start_transaction_and_do_stuff(ctx: &GQLContext<'_>) -> Result<(), Error> {
    let mut anchor = DataAnchorFor1::empty(); // holds client object
    let tx = start_transaction(&mut anchor, ctx).await?;
    // code that uses the transaction
    Ok(())
}
idle pollen