4 ms·
The C++ smart pointers dont prevent multiple threads from mutating the pointed-to data at the same time; multiple threads can both access a unique_ptr at the sa
by mrtracy 2y ago
The C++ smart pointers dont prevent multiple threads from mutating the pointed-to data at the same time; multiple threads can both access a unique_ptr at the same time and mutate its contents. Rust requires shared pointers (Arc) to also explicitly implement some sort of Mutex-equivalent runtime safety check in order to mutate the data. Rust also has explicit notion of thread ownership, and whether individual types are safe to pass to different threads; if a construct is not thread safe, Rust will prevent you from using it in multiple threads.
As a benefit of the thread-safety notion, Rust can have two reference-counting pointer types: Arc, which uses atomic reference counting and is roughly equivalent to std::shared_ptr, and Rc which does not use atomics. Rc cannot be used across multiple threads at the same time, and the borrow checker will prevent you from doing this.
Rc is appropriate for data structures which internally benefit from multiple pointers (e.g. graphs) but where all of that information is internal to a single data structure - this becomes available without paying the price of atomics.
- plq 2y agoSo ARC is something like the following? template <typename T> struct Locker { using M = std::shared_mutex; struct Locked { Locked(mtx, value) : m_lock(mtx), m_value(value) {} // operator->, operator*, get, etc. private: std::lock_guard<M> m_lock; std::shared_ptr<T> m_value; }; struct Shared { Shared(mtx, value) : m_lock(mtx), m_value(value) {} // operator->, operator*, get, etc. private: std::shared_lock<M> m_lock; std::shared_ptr<const T> m_value; }; Shared shared() { return Shared{m_mutex, m_value}; } Locked locked() { return Locked{m_mutex, m_value}; } // a nice forwarding ctor that prevents null m_value private: std::shared_ptr<T> m_value; M m_mutex; };
- fathyb 2y agoRust `Box` = C++ `std::unique_ptr`, both have the same ABI (just pointers) Rust `Arc` = C++ `std::shared_ptr` Rust `Rc` = C++ `std::shared_ptr` but using a simple integer instead of an atomic so it is not thread safe `Arc` and `Rc` do not allow you to mutate their contents directly so instead you should use "interior mutability" using something like a `Mutex` (thread-safe) or `RefCell` (not thread-safe), which have runtime checks to ensure no undefined behaviour is introduced. So `Arc<Mutex<T>>` makes it possible to mutate `T`, but `Arc<T>` cannot. Some types like atomics do not require mutability at all, so an `Arc<AtomicBool>` can be mutated directly. An example of a big C++ codebase using something similar is Chromium, where `std::shared_ptr` is forbidden and `base::RefCounted` (Rust `Rc`) and `base::RefCountedThreadSafe` (Rust `Arc`) should be used instead. WebKit does this too.
- steveklabnik 2y ago> both have the same ABI (just pointers) This is not actually true, but it's close enough for your purposes here. But just to be clear about it, see stuff like this: https://stackoverflow.com/questions/58339165/why-can-a-t-be-passed-in-register-but-a-unique-ptrt-cannot https://stackoverflow.com/questions/58339165/why-can-a-t-be-...
- fathyb 2y agoAnother reason it is not true: Rust has fat pointers, eg. `std::unique_ptr<const uint8[]>` and `Box<[u8]>` both contain the same allocation data, but `Box` will be 128-bit on 64-bit systems.
- bitcharmer 2y agoWhere can I find details like this about Rust?
- fathyb 2y agoThe Rustonomicon is a good start, on fat pointers: https://doc.rust-lang.org/nomicon/exotic-sizes.html https://doc.rust-lang.org/nomicon/exotic-sizes.html > Because they lack a statically known size, these types can only exist behind a pointer. Any pointer to a DST consequently becomes a wide pointer consisting of the pointer and the information that "completes" them (more on this below).
- HarHarVeryFunny 2y agoWhat's the utility of having a 128-bit pointer on a 64-bit system ?
- fathyb 2y ago`Box<[u8]>` stores the pointer and its length (2 x size_t), `std::unique_ptr<const uint8_t[]>` only stores the pointer. That's for slices, for dynamically sized types (eg. `Box<dyn ToString>`) it contains a pointer to the virtual table. https://doc.rust-lang.org/nomicon/exotic-sizes.html https://doc.rust-lang.org/nomicon/exotic-sizes.html
- School-Cotton 2y agoNo, Arc doesn’t require a mutex if you don’t plan on mutating the underlying value.
- HarHarVeryFunny 2y agoThanks. So basically Rust is combining object ownership and thread safety while C++ keeps thread safety separate, which would seem to provide more flexibility, but also lets you shoot yourself in the foot. Just thinking out loud, I wonder if C++ could better address this by also having a class of thread-aware smart pointers? -- but the problem is that C++ always has the old/new (C, C++) way of doing things - pthreads vs std::thread, std::mutex, etc, so even if the language provides easier ways of writing bug free code, there is no way to force developers to use those facilities. In C++ there is also the issue of how to make statically allocated data structures thread safe in an enforceable way. Another kind of smart reference object, perhaps? Disallow global objects not accessed by such references? C++ (which I have used since long before C++11) really wants to be two conflicting things - encompassing C's low level role as the ultimate systems programming language with no guardrails, while also wanting to compete as a much higher-level safer language for application developers. Perhaps the two safe+unsafe roles can be better combined into one language if one were to start from scratch. I'm not sure that Rust gets it right either - erring in the other direction by not being flexible enough.
- School-Cotton 2y agoIn what ways do you think Rust is not flexible enough? I ask because I can think of a few ways it’s less flexible than C, but I also think that effect is massively overstated by people who aren’t familiar with the language. There are OS kernels written in Rust, for example.
- HarHarVeryFunny 2y agoFrom what I've read it seems that certain types of data structure (incl. anything with potentially circular references) are difficult to write in Rust - you are more fighting the language than it helping you. I'm really comparing to C++ rather than C (where of course anything is possible, as long as you DIY).
- School-Cotton 2y agoYes, data structures with cyclic references are a bit harder to write in Rust than in C or C++. But it’s not impossible. And IMO, you write those so rarely that it really doesn’t matter.