How is ARC/RC useful?

I think questions about ARC/RC is common, so I read some articles about it. However, I still don't get why it's useful. I have some questions

  • Why is it useful over the normal reference like &veriable?
  • Are there any cases ARC/RC is useful itself or its useful with some other smart pointer such as Cell, RefCell and so on?
  • When should I use the normal reference vs ARC/RC?
  • Even if the original variable (let original_var = Rc::new(value)) is dropped and some other variables has the clone (Rc::clone()), the Rust compiler doesn't error out or is it invalid?

thanks

Normal references are non-owning, they need the referenced value to be owned by something else, therefore they're tied to specific stack frame. Rc/Arc are owning - they can exist on their own and can be moved freely.

Nitpick - Cell and company are not, strictly speaking, "smart pointers", since they're not pointers - they store their data inline.

Anyway, Rc/Arc is indeed not very useful without some kind of interior-mutable primitive. This primitive can be hidden though - e.g. you can have Rc<File>, since File is usable even through the shared reference, all the shared mutability is on the OS level. And there can also be cases when you create something once (e.g. app config) and then pass it around - in some cases, this will be easier with Rcs (and when app is multithreaded, you're often forced to use Arcs).

I'd say you almost never want to store references (in structs or enums), but you almost always want to pass references to functions (when it's possible, of course, i.e. when this function doesn't end up storing this reference).

There's no "original variable", as far as compiler is concerned, all clones of the same Rc/Arc are the same. The value will be dropped when the last pointer is dropped, and it doesn't matter which one it is.

8 Likes

Arc is for sharing data between multiple threads. Rc is a special case where you do not need multi-threading. Here is an example from my database software:

/// Access to shared paged data.
pub struct AccessPagedData {
    writer: bool,
    time: u64,
    /// Shared Page Data.
    pub spd: Arc<SharedPagedData>,
}

So this gives access to a set of database pages. In this case to have a coherent picture, there is a time involved, so readers get database pages for a particular time even if some other thread is updating pages (appropriate copies are made so this is possible).

If you look at the definition of SharedPagedData you will see Mutex and RwLock, these are typically used to control access to shared data if it is not simply read-only.

pub struct SharedPagedData {
    pub ps: RwLock<Box<dyn PageStorage>>,
    pub stash: Mutex<Stash>,
    pub psi: Box<dyn PageStorageInfo>,
}

Because it represents shared ownership. A reference doesn't have ownership.

Use (A)rc when you need shared ownership, and use a regular reference when you don't.

Isn't that the whole point of reference counting?

1 Like

Here's an article which explores Rust types across two axis: Unique vs Shared and Borrowed vs Owned.

Unique           Shared          
Borrowed          &mut T &T
Owned T
Box<T>
Rc<T>
Arc<T>

They can be, when you need to clone a lot.[1]

I would phrase this more like: as far as the compiler is concerned, the clones are independent, like cloned Strings are independent. (It's the library implementation that handles the shared ownership details.)


  1. Which is maybe what you meant with the app config example. ↩ī¸Ž

7 Likes

thanks all. that helps me understand Arc/Rc

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.