So why even have such a thing in a language designed for concurrent programming from the ground up?
Arc should be called Rc, and that's it.
So why even have such a thing in a language designed for concurrent programming from the ground up?
Arc should be called Rc, and that's it.
Just because a language is designed for concurrent programming, it shouldn't make it impossible to achieve full single-threaded performance, as long as you're not compromising safety.
if you have only 1 thread, you don't atomic, and thus not using atomic reference counts is fine
but if you have more than 1 thread, you can't use a non-atomic refcount, so you can't use Rc but must use Arc.
"but that's such a simple change, just change the decl with a 1 char addition! Pluse, Rust won't let you do bad stuff if you've forgotten to change the type".
I guess I'm just old. Old enough that I've already implemented all the data structures and methods I need in C++, including safely passing around shared_ptr<T>.
You should write code to minimize the reference count bumps; they are waste of time whether atomic or not.
If the code spends 0.5% of its time bumping references, and you magically reduce that to zero using alien optimization technology, that only gives you a 0.5% improvement.
If the code spends 10% of its time bumping references up and down, something is wrong.
It's even possible to share an Rc-managed value across threads without switching to Arc, as long as the other thread(s) never needs to change the reference count and can be "scoped" (https://doc.rust-lang.org/stable/std/thread/fn.scope.html) to some lifetime that some particular Rc outlives.
That could similarly work for Arc values to minimize the atomic bumping.
In C++ equivalent roadblocks are not sign posted. You may not even realise you're in trouble until some very strange errors begin to happen.
For the second paragraph, that depends a great deal on (a) what the mechanism used to "send a (shared, ref-counted reference thing) to another thread actually means and (b) what objects are used to accomplish this. Certainly simply writing the address of a shared_ptr<T> in C++ will work out as you indicate. But that's not the only way to do it. Rust's benefit comes from you being "unable" to do it an unsafe way; C++'s benefit comes from the fact that somebody has probably implemented the safe way in C++ already :)
You're an experienced C++ programmer, you already know what the "safe way" will be in C++. "Just don't make any mistakes". There's no possible way to benefit from multi-threading and yet use arbitrary non-thread-safe features magically without problems, the "genius" of C++ is finding a way to blame you for things you can't do anything about.
So the "diagnostic against misuse" you're concerned about is a necessary part of the compiler anyway.
Indeed, although Rc has this line:
impl<T: ?Sized, A: Allocator> !Send for Rc<T, A> {}
(which means roughly "You can't send this type to another thread")It also has these lines:
// Note that this negative impl isn't strictly necessary for correctness,
// as `Rc` transitively contains a `Cell`, which is itself `!Sync`.