Search code examples
rustsmart-pointersreference-counting

Is there an operation for Rc or Arc which clones the underlying value and returns it to the caller?


I'm looking for something roughly like this take, but atomic:

impl<T: Clone> for Arc<T> {
    fn take(mut self) -> T {
        Arc::make_mut(&mut self);
        Arc::try_unwrap(self).unwrap()
    }
}

In other words, I want Arc::make_mut which returns the value itself, rather than a mutable reference.


Solution

  • We can use the * deref operator to address the underlying value inside of an Rc or Arc, and then call .clone() to return a new owned clone of that value (assuming it's clonable).

    use std::rc::Rc;
    
    fn main() { 
        let rc = Rc::new("Hello".to_string());
        let mut cloned = (*rc).clone();
        cloned.truncate(4);
    
        // verify that it's modified
        println!("{:?}", cloned); // "Hell"
        // verify that the original was not
        println!("{:?}", rc); // "Hello"
    }
    

    The Rc/Arc semantics will prevent any mutable references from being created while your reference exists, so this operation is thread-safe; the data can't be changed while you're cloning it. You also don't need a mutable reference to the original underlying value, because you're not modifying it.

    In some cases, Rust lets you omit the * deref operator: it will implicitly dereference a non-mutable pointer type if you try to call a method that doesn't exist on the pointer, but does exist on the underlying value. However, we need to be explicit in this case because a .clone() method does already exists on Rc/Arc: it's used to create a new reference to the same value. We don't want to call that, so we need to explicitly dereference to access the inner type's .clone() instead.

    We can also tell Rust which .clone() method we want by explicitly calling it through the appropriate type, and the compiler will implicitly apply as many dereferences as necessary.

    use std::rc::Rc;
    
    fn main() { 
        let rc3 = Rc::new(Rc::new(Rc::new("Hello".to_string())));
        let mut cloned = String::clone(&rc3);
        cloned.truncate(4);
    
        // verify that it's modified
        println!("{:?}", cloned); // "Hell"
        // verify that the original was not
        println!("{:?}", rc3); // "Hello"
    }