Beginner lifetimes/ownership question

Lets say I want to have 3 structs

pub struct XY_global {
    x:  f32,
    y:  f32
}
pub struct NormalizedBox {
    b_L: XY_global,
    b_R: XY_global,
    t_L: XY_global,
    t_R: XY_global,
   
}
pub struct BoxSides {
    b: (XY_global, XY_global),
    l: (XY_global, XY_global),
    t: (XY_global, XY_global),
    r: (XY_global, XY_global)
}
impl<'a> BoxSides {
    fn new (s: &'a NormalizedBox ) -> Self {
        Self {
            b: (s.b_L, s.b_R),
            l: (s.b_L, s.b_R),
            r: (s.b_L, s.b_R),
            t: (s.b_L, s.b_R)
        }
    }
}

Except the BoxSides I want to be able to pass an instance of NormalizedBox to use its values to create some tuples.
I get this error on the impl of BoxSides

cannot move out of `s.b_R` which is behind a shared reference
move occurs because `s.b_R` has type `XY_global`, which does not implement the `Copy` trait

I believe it has something to do with lifetimes but I am stuck and keep looping back around trying things over and over. What is the correct way to do this? None of the structs need to mutate any data they just need to reference it but I just cant get it.

Thanks for any help

It's not lifetimes in this case, but it is an ownership issue. By default Rust types are "affine" types -- which means once you move them from one place to another, the place they were moved from is considered unitialized / no longer usable. You can't move from underneath a reference because a reference must always point to a valid value -- no unitialized fields allowed.

However, if types implement Copy, then any place a move would have been performed, a copy is performed instead. This is the same low-level operation -- memcpy some bytes -- but the source location doesn't become unititialized when it's a copy.[1]

To fix things for your case, add:

+#[derive(Copy, Clone)]
 pub struct XY_global {
     x:  f32,
     y:  f32
 }

Or if you don't want the copies to be implicit, only derive Clone and then call .clone() in a lot of places instead.

To make things more idiomatic, follow the naming guidelines in the warnings and also:

-impl BoxSides<'a> {
-    fn new (s: &'a NormalizedBox ) -> Self {
+impl BoxSides {
+    fn new (s: &NormalizedBox ) -> Self {
         Self {
             b: (s.b_L, s.b_R),
             l: (s.b_L, s.b_R),
             r: (s.b_L, s.b_R),
             t: (s.b_L, s.b_R)
         }
    }
 }

  1. Many of these moves/copies can be optimized away. ↩︎

2 Likes

Is there no way to do it without copying? I want all 3 structs to reference the same value. Or is copying best practice / the most performant?

You're going to be copying something around. In this case it might be a 64bits value (two f32) or it might be a 64bit[1] reference. Probably you want the values.

I'm not sure what your background is, but note that references to values are different types than values, and are parameterized by lifetimes. You can't go the "borrow everything" route without designing your structs to hold borrowed values from the beginning. They introduce indirection and are somewhat like compile-time checked C pointers.

Overdoing it with references -- especially for small pieces of Plain Old Data like this -- is a common newcomer mistake. You'll almost surely end up with a mess of borrow errors and difficulty when you want to actually update a value and so on.

My advice is stick with the values.


  1. assuming a 64bit platform ↩︎

Do you want to do without copying for performance reasons? If so, that's probably a mistake for smallish values, as @quinedot said.

Or is it because you want to change the referenced structs and have those changes reflected immediately via the fields that reference them? That's the kind of thing that is not easily done in Rust, and worth discussing if that's what you were thinking.

Yeah... just think think about the actual size of the "copy". Your XY_global struct is two f32s, which (because there should be no padding needed) will fit into one 64-bit value. On a 64-bit processor like most processors today, a pointer (the address that is being copied around when you pass something by reference) is also 64-bit, so you gain nothing in terms of copying. You just lose in terms of having to dereference the pointer every time the object is accessed.

Except if your struct really grows a lot, structs of simple data like numeric values are almost always fine/better to copy.

Thanks both for replying. I’ll just copy. These are being used to compile into wasm and the code needs to be relatively performant. Thanks again

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.