Serious question: can I just use smart pointer like feature of Rc/Arc combing with Mutex or RWLock instead of fighting borrow checker? I know this practice is shunned upon by Rust purists but I like to see how practical it is in real life and project. Especially around using Rust as a safer but more performant language instead of pushing for ultimate zero-cost abstraction and top notch performance.
Although I can't recall them off the top of my head, several guests on the Rustacean Station podcast have suggested exactly that when one is getting their feet wet with Rust (put .clone() everywhere, put Arc<Mutex<..>> everywhere, ..etc).
The idea is that once you are familiar with other aspects of the language (syntax, sum types, traits, modules), you can go back and try to master the borrow checker.
Probably, yes. But note that ref counters will leak memory in the presence of cycles.
You need to either ensure that your pointer graphs contain no cycles (in which case you could probably also model it using ownership) or have some strategy for discovering and breaking those cycles (in which case ref counting isn't buying you that much).
In other words, if your data is naturally modeled using ref counts, then, yes, Rc is great. If not, it's not. And it's probably worth noting that most managed languages don't use ref counting, so the set of problems that are naturally modeled that way appears to be relatively small compared to tracing GC.
The main drawback of trying to do this might be that the syntax is unwieldy. If you have one big shared object, or shared objects only in a specific part of your program, it's no big deal, and it arguably helps call attention to what's going on. But if everything is using Arc<RwLock<...>>, you'll have .write().unwrap() or similar on every line, and it'll feel terrible.
The sibling comment mentioned cycle leaks, and in addition to that I'll add the runtime panics or deadlocks associated with locking the same thing twice.
Agree that I should not just stick Rc everywhere. The use of borrow checkers makes a lot of sense at a macro scale, i.e: libraries, APIs. But, I’m just wondering the justification for using Rc in a smaller scope, say inside my own implementation of a trait. If I am to provide external APIs then yes, sticking Rc everywhere would not be a good idea ergonomically.
One high level problem with learning Rust is that a lot of the patterns we use to work with the borrow checker are pretty non-obvious. (Things like using indexes instead of references, and avoiding methods that keep &self borrowed.) To the extend that Rc and Arc let you avoid learning those patterns, I agree with folks who say not to use them too much. But if you know how you might solve something without Rc, and you want to try it with Rc anyway to see how it feels, that's totally reasonable.
In addition to munificient's response, I'd point out that this will likely have a significant performance cost. Since performance is one of the main reasons of using a low-level language like Rust, it should give you pause.
I'm not saying there's anything bad with Arc + Mutex - they're certainly useful tools and great in some scenarios; it's just that I wouldn't reach out to them just to "avoid fighting the borrow checker". If your code can be written without those tools, it's best to do so.
Refcounting had been a very common way to implement resource cleanup for object graphs back in 90s already, in a much more resource-constrained environment. E.g. COM, which is widespread in Windows to this day, is all refcounted.