In this video (starting at 17:20), Alex Crichton explains the memory management via reference counting (the standard "Rc" and its atomic variant "Arc"):
Please note that this video is based on an older version of Rust.
Is there anything in particular you have questions about? For example: when to use reference counting? How you might implement a reference-counting system? How to use Rc specifically? This might help us answer your questions more effectively!
I do tend to think of Reference Counting as a type of garbage collection, even in the context of rust (but you have to look at the term in a slightly more generic context). You can think of "garbage collection" as meaning "how do I know if I can free this resource (like memory)", then reference counting is a way to do this.
As an analogy, let say you are sitting in your living room watching TV with your family, and you want to know when you can turn the TV. Here is what most people do in this scenario: when they leave the room, they look to see if there is still someone in the room watching TV. If there is, they just leave the room and leave the TV on. But when the last person leaves the room, they see that no one else is watching TV, and turn it off as the leave. Let's call this "TV watcher counting"
To make this analogy slightly more accurate, we have to figure out what we mean by a "reference". Let's say that not everyone in the room is watching TV -- some people are reading books. When someone sits down to watch TV, they put a little sticky note on the TV, which says "I am watching this". When they leave the room, they remove their little sticky note. Instead of looking to see if anyone is in the room before turning off the TV, they look to see if there are any sticky notes left. If not, turn off the TV! In this example, the sticky notes are references.
The question was more about when and how to use Rc and Arc.
I kinda got it with threads, but I don't get it without them, since only one function at a time can run and so every resource can only be owned by this function.
It's not quite true that only one function can run at a time, but that's not relevant to how rust tracks "ownership". To quote the very excellent rust book:
Variable bindings have a property in Rust: they ‘have ownership’ of what they’re bound to. This means that when a binding goes out of scope, the resource that they’re bound to are freed.
we can start to see the problem that Rc is trying to solve here. Since a variable (like Gadget.owner) owns the thing it is bound to, we can't have multiple variables owning the same thing. This would be a problem if we had multiple Gadgets with the same Owner struct. Instead of having Gadget.owner be an Owner directly, it can be an Rc<Owner>. The Rc object is the thing that actually owns the Owner. Conceptually there is only one Rc object, but in reality whenever we clone it we are creating multiple Rc objects that point to the same underlying data.
(I hope that made sense, it was a little confusing with two meanings of "owner")
You now might be asking yourself: How come multiple Rc objects can each own the same data, but I can't? The answer is that Rc uses unsafe code to play some tricks. Here "unsafe" means the compiler can't verify the safety of the code, so the programmer has to say "trust me, this is OK".
Because of Add trait isn't implemented for Rc<_>, and the compiler knows that that's not a case to transparently deref it, you get an error about Binary operation cannot be applied to this type. So, you need to explicitly deref the reference counter and then you can do such operation.
Not every trait in existence is implemented for Rc/Arc of a type. For things which are implemented transparently, see Rc in std::rc - Rust. Ord, Hash, Display, Debug, Pointer and Borrow are implemented - I guess Add/Sub/Mul just weren't general enough to get inclusion in this. It is fairly easy to just dereference though.
If I understand correctly, this also means that when you call .clone() on a Rc<T> or Arc<T> object you're just cloning a pointer and not the underlying data, which means that for larger data structures this is also more performant and efficient, correct? I'm thinking of the use-cases when you're working with threads/closures and sometimes have to clone some values captured by the closure in order to avoid move errors, etc.