Yup. I might add that `shared_ptr` is thread-safe, while `Rc` isn't (and is enforced by the compiler to not migrate between threads). Because of that `Rc` tends to be much faster than C++ `shared_ptr`, since you only opt into the thread safety if you need it.
Instead of saying that thread safety is opt-in, I would say that thread safety (at least, avoiding data races) is mandatory in Rust and enforced by the type system... but you only pay for sharing if you need it.
C++ has a "pay for what you need" mentality, but do to weak aliasing guarantees, you have to pay for atomic ops on `shared_ptr` whether you need them or not. Just like you have to pay for using `string` because two strings can't safely share data without runtime checks: either you pay for COW (which is less common these days) or you pay for copying. In Rust, the safety of string aliasing is enforced by lifetime analysis.
> In Rust, the safety of string aliasing is enforced by lifetime analysis.
Still I have seen a lot of examples where the static strings already present in the data sections have to be explicitly created on the heap just to be used somewhere else. Something like (I write in pseudo, I never programmed in Rust)
return (put on heap)"some static string"
and it will be probably copied from the data section to the heap just for the content to be read (from the copy on the heap instead of the executable data section) and then the copy on the heap discarded. That's how I understood it at least, please correct me if I'm wrong. Why not having some COW mechanism for such cases built-in in the language, avoiding unnecessary allocations, copying and deallocations?
In C++ this is a pretty common thing due to the fact that `const char *` is pretty annoying to work with, so people often use heap-allocated `std::string` instead. In Rust `&'static str` and `StrBuf` are on more equal footing, so it's not necessary to place objects on the heap to make it easier to work with them. Still, some people do it accidentally, so we've recently made some changes to make it harder to shoot yourself in the foot in terms of performance, most notably removing the `~"foo"` syntax for heap allocated string literals (which also had the side effect of making the language easier to read).
lines. Can you please point me to some complete-enough example of the new "lighter" string return? Thanks.
Edit: I guess the key is in 'if you must return the string on the heap.' Why should be common that anybody must return the string on the heap? Why shouldn't be possible to program so that only the receiver must place something on the heap due to his own needs? Why shouldn't the one doing return be able to always return "something"? Couldn't it be made possible in the language? The one doing return shouldn't need to put something he knows it's static on the heap, especially as most probably the receiver will just discard it as soon as it reads the content.
Just use `return "something"`. That returns a `&'static str`. If you must return a heap allocated string, the you can use `return StrBuf::new("something")`.
Why is there a "must" for returning the string on the heap? Can't it be solved in the language to use the static string up to the point the reallocation is needed?
Rust has builtin types &'static str for strings that live for the entire runtime of the program and ~str for strings that live in dynamically managed heap memory (from the more general ~T for T that live there with T=str). If the function always returns a static string it can return the former type and won't need a ~ or any allocations for that.
If you need something more dynamic... you could have a user-defined type that can hold either and remembers whether it's responsible for freeing the memory, but that doesn't fall out of the builtin types naturally.
As far as I understand, the effective overhead of "free" can be practically zero if the compile-time construction of the static strings would contain some "nothing to deallocate here" info versus any info which anyway has to be run-time maintained to anything that is really allocated. Any non-trivial allocator has a bunch of checks to do (e.g. different-sized small objects are typically allocated in separate blocks, differently to the big allocations etc).
As soon as your language doesn't use C-like zero-terminated characters but anything with more information, it's trivial to avoid these unnecessary copy-to-heap-just-to-use-and-deallocate steps.
As other have said: it can be solved in the library by defining a string type with the appropriate semantics (e.g. the MaybeOwned[1] type they refer to).
What's the archetype use for Rc anyway? I don't see the point in having a reference-counted object with single thread ownership.
Also you only pay to synchronise shared_ptr when you take a copy of the pointer... I can't think of anywhere in a sane, well-structured algorithm where this would bite.