Where to clone an Arc/Rc? getter, setter, neither?

Is there a recommendation on where to do the clone() of an Rc/Arc when transferring such a value?

Those are the variants I could think of:

use std::sync::Arc;

#[derive(Default)]
struct MyStruct {
    my_arc: Arc<()>,
}

impl MyStruct {
    
    fn clone_arc(&self) -> Arc<()> {
        self.my_arc.clone()
    }
    
    fn set_arc(&mut self, arc: Arc<()>) {
        self.my_arc = arc;
    }
    
    fn get_arc(&self) -> &Arc<()> {
        &self.my_arc
    }
    
    fn set_arc_clone(&mut self, arc: &Arc<()>) {
        self.my_arc = arc.clone();
    }
    
}

fn main () {

    let source = MyStruct::default();
    let mut destination = MyStruct::default();

    // clone in getter
    destination.set_arc(source.clone_arc());
    
    // clone in setter
    destination.set_arc_clone(source.get_arc());
    
    // clone externally
    destination.set_arc(source.get_arc().clone());
}
  1. clone() in getter: convenient for the caller; doesn't need any borrows; might result in unnecessary clones when I just want to access the value for reading.
  2. clone() in the setter: has to deal with borrows; might result in unnecessary clones when I pass a newly created Arc.
  3. clone() in the caller: least convenient for the caller; cluttering the code; full control about the clone()operations
1 Like

Rust is all about giving explicit control: sacrificing a performance optimization, even if minor, to save a .clone() call does not go in that direction.

So, regarding the getter, returning a borrowed Arc is definitely the best, and regarding the setter, it should require an owned value.

If you really want ergonomics for the setter, you could use a custom trait:

pub
trait ToArc<T : ?Sized> {
    fn to_arc (self) -> Arc<T>;
}

impl<T : ?Sized> ToArc<T> for Arc<T> {
    #[inline]
    fn to_arc (self) -> Arc<T>
    {
        self
    }
}

impl<T : ?Sized> ToArc<T> for &'_ Arc<T> {
    #[inline]
    fn to_arc (self) -> Arc<T>
    {
        self.clone()
    }
}

// and maybe even
impl<T> ToArc<T> for T {
    #[inline]
    fn to_arc (self) -> Arc<T>
    {
        Arc::new(self)
    }
}

and then require impl ToArc<_> as the (generic) type of your function arguments:

fn set (self: &'_ Self, value: impl ToArc<()>);
1 Like

Thanks a lot. "Make the costs visible" aligns with my intuition.
I had something like your trait in mind but I couldn't have written it this fast :smiley:

Hmmm ... I just noticed that I cannot cast the references into a trait object any more: Rust Playground

fn get_arc(&self) -> &Arc<dyn MyTrait> {
    // mismatched types
    //&self.my_arc

    // cannot return reference to temporary value
    // cannot move out of borrowed content
    //&(self.my_arc as Arc<dyn MyTrait>)
        
    // non-primitive cast: `&std::sync::Arc<()>` as `&std::sync::Arc<dyn MyTrait>`
    //&self.my_arc as &Arc<dyn MyTrait>
}

Another downside is, that I cannot create new values any more (cannot return reference to temporary value) - just return references to existing values.

To return reference of something, you should hold its owned value somewhere. Arc<dyn MyTrait> contains fat pointer so it even has double size of Arc<MyType>.

1 Like

I have a trait that demands a return type of &Arc<RwLock<dyn MyTrait>> but I have a field of type Arc<RwLock<MyStruct>> (where MyStruct: MyTrait).
This works fine without the reference but then I'd always have to clone the result.

Yeah, that's not possible in general. To return a reference of Arc<RwLock<dyn MyTrait>>, you must hold this 2-pointer-wide type somewhere, not something that can be coerced to this type.

As @Hyeonu pointed out, you cannot return a &Arc<dyn Trait> unless you hold the actual Arc fat pointer somewhere in memory (e.g., in your example, you could return &self.traited_arc).

So you need to return &Arc<TypeImplementingTrait>. In your example, that can be &Arc<()>, or, if you wish some type erasure, &Arc<impl Trait>:

impl MyStruct {
    #[inline]
    fn read_arc (self: &'_ Self) -> &'_ Arc<impl MyTrait>
    {
        &self.my_arc
    }

    #[inline]
    fn clone_arc (self: &'_ Self) -> Arc<dyn MyTrait>
    {
        self.get_arc().clone()
    }
}
1 Like

Definitely never set_arc_clone, since that's the worst of both worlds. Accept already-cloned Arc if you need to keep it.

For getters:

  • If callers are likely to want to keep the value, return -> Arc<dyn Trait>
  • If callers are likely to only inspect the value without keeping the reference, return -> &dyn MyTrait (unwrapped from Arc).
  • If both are equally likely, &Arc seems like a compromise.
3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.