top | item 40265512

(no title)

mrtracy | 1 year 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.

discuss

order

HarHarVeryFunny|1 year ago

Thanks.

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.

umanwizard|1 year ago

In 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.

plq|1 year ago

So 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|1 year ago

Rust `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.

umanwizard|1 year ago

No, Arc doesn’t require a mutex if you don’t plan on mutating the underlying value.