Hacker News new | past | comments | ask | show | jobs | submit login

[flagged]



Thanks! Could you point me to the C++ primitives that prevent cross-thread access of non-thread-safe data? I'd honestly love to learn about this.

If it's not asking too much, could you also point me to the C++ primitives that prevent dangling pointers as well?

EDIT: Also, the primitives that prevents the mutation of what a const reference refers to? (Much in the way that shared references and exclusive references can't co-exist in Rust)?

I'd hope the C++ primitives for the above all perform their job at compile time of course.


Ignoring GP's trolling, closest would be https://clang.llvm.org/docs/ThreadSafetyAnalysis.html


Thank you! IIUC correctly, the TLDR of that is:

    An extension to C++ that adds (pretty much) a type system on top of C++ to enable static analysis and prevention of race conditions. Widely used at Google.
Super interesting! That's exactly what Rust is doing too, except I think Rust's version (which pretty much relies on two "traits", `Send` and `Sync`) is much simpler, much more ergonomic, and much more complete.


He can't because he doesn't understand what you're asking.


Tbh, I'm also a Rust beginner and know even less about C++, so I'm sincerely trying to learn here.


C++ doesn’t provide any of the checks being talked about here. There’s nothing much to learn; they just don’t exist.


Thank you! Still waiting on you @ncmncm, if you think differently.


If you can see it at all, it will be an atomic type.


I skimmed docs for std::atomic, and I believe what you're suggesting is a super-brief version of what was outlined here[0].

If so, thanks!

Through I don't yet "see it" that C++ has safety primitives that "equal" that of Rust's, I've learnt a lot about C++'s safety features from other folks' replies in this thread. So again, thanks!

[0] https://news.ycombinator.com/reply?id=32824168


Trolling is unwelcome here.


By definition you can't safely access "non-thread-safe data" from another thread unless it was already safe to access despite being "non-thread-safe", but there are also many ways to make something thread safe.

You can prevent dangling pointers using strong/weak pointers or many other ways depending on the context, most examples are quite contrived though and the only "dangling pointers" I see are usually those relating to resources/handles to things from the OS where the lifetimes need to be managed more carefully, i.e. audio/video/gpu/etc, how would a rust program handle a handle to the currently playing audio device being unplugged and invalidating your handle? Every OS also has its own quirks, so how would it work cross platform?

Constexpr primatives/functions have compile time guarantees and can't be changed.


About the threading stuff, I think I wasn't clear initially. Maybe a rephrasing/simplification of my question will help: Does C++ have a compile-time way of ensuring, say, that mutex-protected data isn't accessed without acquiring a lock on the mutex?

> You can prevent dangling pointers using strong/weak pointers

I presume you're talking about unique_ptr/shared_ptr/weak_ptr right? If one avoids raw pointers and uses those, I think you cannot have a shared reference without ref counting right? (because shared_ptr uses ref-counting). Not that this invalidates your point, just thought it was useful to point out.

> how would a rust program handle a handle to the currently playing audio device being unplugged and invalidating your handle?

I've never had to deal with that situation, but I presume some mechanism similar to how the mutex in the stdlib has a .lock() method that returns an Option<MutexGuard<_>> (which forces the user to acknowledge/handle the case of a poisoned mutex).

> Constexpr primatives/functions have compile time guarantees and can't be changed.

Consider the c++ code here:

    int a = 4;
    const int &b = a;
    a += 1;
Such a thing would be disallowed in Rust, which (though hard to see from this example) is the exact mechanism that prevents a Vec being modified while it is being iterated on. Does C++ have primitives that provide such guarantees?


Practically all valid Rust can be rewritten as valid C++. To make C++ reject most invalid thread-based code that Rust rejects, you can mark cross-thread-shared references as const&, atomic fields as mutable, and write a Mutex<T> class which wraps a mutable T, and only allows accessing a T& through a Mutex const& by locking the mutex. So implementing Rust-style threading in C++ is viable, but extra work to write a Mutex<T>, and the compiler lacks borrow checking to prevent you from making lifetime and overlapping &/&mut mistakes and committing data races. (http://www.drdobbs.com/cpp/volatile-the-multithreaded-progra... proposes using volatile& for this purpose, but many regard this as actually a bad idea.) Though in practice most C++ codebases sprinkle mutexes ad-hoc, relying on at best comments describing what data is protected by what mutex, with predictably disastrous results.

I feel Rust is a better language than C++ at implementing correct multithreaded code, but is so constraining that it makes correct single-threaded code hard to implement when it doesn't fit neatly into tree-shaped ownership (eg. graphs, GUI trees, and extending existing program architectures).


Learnt a lot from this. Thanks!


Like I said, use constexpr.

  constexpr int a = 4;
  auto& b = a;
   
  a+= 1; // compile error
  b+= 1; // compile error

Trying to make b constexpr with "a" as an int is also a compile error.


That would unnecessarily constraint `a` to be known at compile time though? I imagine that would be incredibly limiting.

E.g., if you're implementing an iterator that takes a reference to the length of the container being iterated, and you require that that reference is constexpr, that means the container will have a fixed, compile-time-known, size.

Since you didn't reply to the other parts of the comment, I'll presume that C++ doesn't have the mutex I'm talking about (apart from this attempt[0], which I just learnt about and commented on in a different reply), and that it's not possible to have non-ref-counted shared references while statically avoiding dangling pointers.

[0] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html


A lot of constants are known at compile time and constexpr solves a lot of problems. However, you do not need constexpr to solve your example- this is still a compile error:

void iter(std::vector<int>& vec) {

     auto& vec_size = vec.size();

     vec_size += 10; //compile error 
}

There are many ways of forcing compile errors for mutexes that people have built, but they all come with their own trade-offs.

I've not encountered a problem where I specifically needed "non-ref-counted shared references while statically avoiding dangling pointers", so I'm not an expert on it.


What I'm referring to with regards containers is: preventing containers being modified while they are being iterated on (which is usually bad, because you will probably invalidate the iterator when modifying the container). That is what is prevented by Rust's references rules, and to my knowledge, isn't possible to protect against in C++ (statically).

Thanks for the replies about the other stuff!


> how would a rust program handle a handle to the currently playing audio device being unplugged and invalidating your handle?

You would either represent this as all the operations having a Result which can be Unplugged or whatever, or you might decide (as the designer of that library) that you'll eat the error and silently ignore operations when unplugged, offering an unplugged() predicate so that callers can check whether they got unplugged if they care.

If you mean, what if the system is allowed to just invalidate handles we've got for some reason, without telling us about that, and then after invalidation they just mustn't be used, then you're screwed, regardless of programming language, that's a pretty bad design and there's nothing to be done about it.

A more common design (e.g. for OS file handles) has an explicit call where you give back the handle (close in the case of file handles), promising you won't use it again. If the handle is meanwhile broken for some reason anyway (e.g. user pulled out the USB stick with the file on it) then the OS will tell you about the problem, but you still need to acknowledge that by closing the broken handle, you don't just suddenly find there's a different file behind your existing handle now. It's no trouble to represent this properly in Rust.

Let's explain a bit more about why Rust cares about thread safety. Rust types can have marker traits named Send and Sync. Send means "You can safely give this type to another thread" and Sync means "You can safely give references to this type to another thread".

For example lets say I have a Goose, which is three 32-bit signed integers named x, y, z, plus three booleans flapping, honking and hissing. All six of those elements are Send, so Goose is Send. I can give a Goose to another thread no problem.

But the type Rc<Goose> isn't Send. Rc is a reference counted smart pointer, like shared_ptr<Goose> from C++ except it's not for threaded software. It's a little bit faster, especially on some CPUs, but you can't safely use it across threads. Rust has another reference counted smart point Arc, and Arc<Goose> is Send for the same reason shared_ptr<Goose> is thread safe in C++ -- it uses atomic integers for reference counting.

Because Rust tracks the marker traits for me, I don't need to carefully read documentation for a new type I'm using FunkyTractor to check whether it's thread safe. If it's Send then I can give it to another thread, and if it isn't then that program doesn't compile and I go "Aww" and use say a Mutex to wrap my FunkyTractor so that multiple threads can access it safely, then it will compile.

Only the people actually innovating tricky thread safety stuff (e.g. building your own spinlock) need to care about this, and make decisions like "Should this type I'm creating say it is Send?" for everybody else it's automatic.


From hackernews guidelines:

> Comments should get more thoughtful and substantive, not less, as a topic gets more divisive.

> Please don't post shallow dismissals

If you have a real rebuttal, add it. Otherwise don't bother commenting.


Why not provide some sort of useful statement of how a lack of compiler checked memory access provides compiler checked memory safety?

To quote you: Trolling is unwelcome here.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: