Rust can handle reference counting very elegantly with Rc
. It seems many members of the community prefer not to use this and instead use the ownership/borrowing semantics of the language. This results in simpler programs to write, but aside from circular references is it ever necessary?
Clearly across threads things get more complex, so to simplify what I'm trying to learn, "in a single threaded application is reference-counting ever required except as a write-time optimization?" Is this an anti-pattern at the higher levels of Rust skill?
In computer science, reference counting is a programming technique of storing the number of references, pointers, or handles to a resource, such as an object, a block of memory, disk space, and others. In garbage collection algorithms, reference counts may be used to deallocate objects that are no longer needed.
In each object, keep a count of the number of references to the object. (Add 1 when copying the reference, subtract 1 when clearing or changing a reference.) When the count drops to zero, kill the object.
Reference counting is one such technique. This method is simply keeping an extra counter along with each object that is created. The counter is the number of references that exist to the object, in the C/C++ case this would how many pointers refer to this object.
I think that @kmdreko is basically correct, but an example that's harder to model without Rc
would be something like filtering queries with "heavy" data, down into multiple owners. Basically, any time you deal with a "set" into multiple other filtered sets, where the filters may overlap.
For example, if you gathered 1000 images, and you invoked OpenCV (or whatever) to find in the original set which images had birds, and which images had a tree in them, it's plausible that the result sets overlap. So if you don't use Rc
(or similar), you're either copying these potentially huge images (probably not wanted) or keeping references to them. OK, but then you can't ever free the original set of images, as it's what owns the images that the result sets are referencing. And while there are workarounds to that (only preserve an image when one or more filter says to), then that leads to deeply integrating potentially separate parts of your program because of the memory model you use. Or just use Rc
and it's "accessible" by others easily, and whatever brought it in can be "done" with the set and free everything not referenced, while the parts of the set still used are still preserved.
I agree that Rc
and related can be over-used. I encounter that all the freaking time in C++ and people throwing shared_ptr
all over the place and think they're "good" at that point (until the application or DLL shuts down, and the calls of "why is my application crashing on exit all the time??" start coming). That said, even single-threaded, sometimes ownership needs to be shared, so it can be passed on to an unknown number of consumers for later.
Like with most tools, there are ways to work around certain parts, but that may cause more pain instead.
You should use Rc
when you need shared ownership. Using Arc
is no different, but perhaps its more common since standard threading mechanisms like std::thread::spawn
require ownership.
You would still use Rc
in single-threaded environments if your data is more graph-like (which could have circular dependencies but doesn't have to). In such cases, you cannot use basic references. You could represent graph-like relationships in a normalized fashion and use indexes or ids in lieu of references, but that may not always be favorable or possible. Of course, if you're using Rc
when a structure could be represented hierarchically, using Rc
would be unnecessary.
In addition, there's many reasons why a external function or structure would require a generic type or function to be 'static
. In those situations, either moving, clone()
-ing or sharing (with Rc
) is the solution.
I wouldn't say Rc
itself an anti-pattern, its simply another tool, but there are sometimes ways to avoid it that would result in clearer relationships and/or better performance. I often see Rc
used liberally by Rust newcomers who haven't fully grasped the borrow-checker.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With