6 ms·
I feel that this is a feature, not a bug. While I don't use C++ extensively, I think the Industry needs at least one language, which does not constrain the deve
by g42gregory 4y ago
I feel that this is a feature, not a bug. While I don't use C++ extensively, I think the Industry needs at least one language, which does not constrain the developer in any way. I think such a language is currently C++. It has all kinds of guidelines now to stay in the "safe zone", but there are many developers who (1) need unfettered access to the hardware and/or (2) need all kinds of language capabilities to write an extreme performance code.
- virtualritz 4y agoCould you elaborate what I can do in C++ that I can't do in Rust (using unsafe, if I must)? For context: I've been writing code in C++ for most of my career (25+ years) and being for VFX/3D rendering it was mostly performance critical code. Now I write Rust for some very performance critical tasks in finance ...
- eloff 4y agoI also use Rust now. If you use unsafe, there is little you can do in C++ that you can't do in Rust. However, some things like data races and mutable aliasing are permitted in C++, but not in unsafe Rust, as they invoke undefined behavior. The Rust nomicon has more details about what's allowed.
- virtualritz 4y ago> However, some things like data races and mutable aliasing are permitted in C++, but not in unsafe Rust, as they invoke undefined behavior. Define 'permitteed'. Unsafe is, among other things, exactly that. It tells rustc to permit you to do things that are ... well, unsafe. Including the ones you listed. Maybe you don't understand the meaning of unsafe in Rust? Unsafe causing UB is a possibility. As is taking two &mut refs to the same memory location using exactly that keyword. And that example works for multi-threaded Rust code using a shared resource accessed as such too.
- kaba0 4y agoThe compiler is free to never write out your changes to memory in that shared resource use-case, which you might be depending on. AFAIK rust doesn’t have a standardized memory model which should be needed to write correct code that data races.
- ameliaquining 4y agoThis isn't a difference between Rust and C++; data races are undefined behavior in both languages. If you want two different threads to be allowed to access the same memory without locking, that's what std::atomic (in C++) or std::sync::atomic (in Rust) does.
- cyber_kinetist 4y agoHe’s talking about formal memory models, which are a bit different from what you’re talking about (though you’re correct that they are pragmatically similar.) C/C++ has a formalized memory model that describe the high level logical rules/guarantees for writing safe concurrent code, while Unsafe Rust doesn’t have one explicitly written in spec/paper (although they kinda borrowed a less-formalized version of it from C/C++.) It has to do with ironing out hardware-specific behavior to obey a certain set of rules, while making sure all the compiler optimizations do not perform any invalid transformations against these rules. (The Rustonomicon explains this well in layman terms, and acknowledges the complexity of the issue: https://doc.rust-lang.org/nomicon/atomics.html https://doc.rust-lang.org/nomicon/atomics.html) In practice, it seems like the C/C++ model has some glaring flaws anyway, so I can’t say that Rust’s is really “worse”. But since Rust has this mission to be better than C++ in terms of safety, this is one of thorny issues of Rust that need to be tackled to really let its advantages shine.
- JonChesterfield 4y agoI thought the C11 memory model was considered correct, possibly modulo 'consume' which seems to be ignored. The implementation in terms of types instead of operations has a performance cost but otherwise works. What glaring flaws do you have in mind?
- Thorrez 4y agoWhat data races are permitted in C++?
- tialaramex 4y agoMake some mutable objects, lets say a std::vector of Geese. Spin up two threads, A and B. Make a pointer, or a reference, or by whatever means you prefer, like an index, the same Goose from the vector, and give both A and B that pointer/ reference/ index whatever. Both threads can now change the Goose at the same time. That's a data race. As a result it destroys Sequential Consistency, but that barely matters in C++ because it also is Undefined Behaviour, your program no longer has any meaning. In (safe) Rust it won't let you give both A and B a way to mutate the same Goose at the same time, thus data races can't occur, thus you have Sequential Consistency, and your program has a defined meaning.
- Thorrez 4y agoYeah, although I wouldn't exactly call it "permitted" if it's undefined behavior.
- tialaramex 4y agoC++ has this IFNDR - "Ill-Formed: No Diagnostic Required" in the standard which marks various things as simultaneously not C++ and so the standard doesn't define what should happen, and yet also your standards conforming compiler is not required to diagnose this and tell you about the problem. You can presumably argue that all those things aren't "permitted" but there is no way to detect for sure if you wrote any of them, and if you did your entire C++ program has no defined meaning and might do anything at all. There's a reason this exists. Rice's Theorem says non-trivial Semantic questions are Undecidable. You thus can't always correctly decide whether a program has some non-trivial semantic property, but C++ wants to require a whole lot of such properties. So, when it's difficult they just say the compiler must err on the side of emitting nonsense programs. [Rust takes the other path here, if the Rust compiler can't be sure that your program has the required semantic properties you get an error. This is rare but annoying, typically you can easily modify your program to satisfy the compiler or when you try to do so you realise actually the compiler was right, this program doesn't have the required semantic properties] I am increasingly confident that C++ made the wrong choice here, neither of these outcomes is desirable but the Rust outcome has a negative feedback loop - if changes make Rust's compiler annoy more programmers with spurious errors there's pushback. The C++ approach has positive feedback, as C++ gets less well-defined people's compilers accept whatever nonsense they wrote, nobody tells the committee to stop doing this - until one day it blows up in their face.
- shaklee3 4y agoif you're using unsafe then what's the argument to use rust? the comparison should be what you can't do in rust without unsafe.
- kllrnohj 4y ago> Could you elaborate what I can do in C++ that I can't do in Rust (using unsafe, if I must)? Use existing C++ libraries/code. I love Rust, or at least what I've seen of it since I haven't gotten the pleasure to use it in a meaningful capacity, but it's naive to pretend existing code can just be re-written and therefore existing languages should stop improving. I do think it'll be fascinating to see how the performance of Rust and C++ ends up shaking out head to head. That is, how much is Rust actually sacrificing in performance by reducing the amount of UB (if anything, or if other aspects allow for better performance on average)
- ameliaquining 4y agoThe only operations I can think of that are undefined behavior in C++ but not Rust are signed integer overflow (which Rust defines to panic in debug builds or wrap around in release builds) and out-of-range casts from floating-point to integer (which Rust defines to saturate). Out-of-bounds index access is a borderline case; both languages have a safe index operation wherein out-of-bounds accesses trap, and a fast one wherein they're undefined behavior. However, C++ gives the convenient bracket syntax, which programmers are likely to reach for by default, to the fast behavior, and requires the safe behavior to be spelled out ("at"), while Rust does the reverse (the fast behavior is "get_unchecked" or "get_unchecked_mut"). Also it seems that not all relevant data types in the C++ standard library support the safe behavior, which is unfortunate. In all other cases (that I can think of), Rust prevents undefined behavior at runtime not by changing it to do something defined at runtime instead, but by requiring the programmer to prove to the compiler that the undefined behavior can't happen. This may be annoying, and may encourage programmers to resort to things like RefCell that have runtime costs in order to avoid the difficulty of such proofs, but it should never stop you from doing the unsafe maximum-performance thing if you really want to. Rust can also do some optimizations that C++ can't; it has additional forms of undefined behavior, like mutable aliasing and invalid UTF-8 strings, that the compiler can in principle exploit. Probably the more important advantage, though, is that Rust's maintainers can freely make changes to its compiler and standard library that don't preserve compatibility with existing compiled code (as opposed to existing source code), because the language has never promised ABI stability and (unlike C++) doesn't have an entrenched community of users who count on ABI stability and will complain if it's broken. Almost all Rust binaries statically link all dependencies except for libc, and build all statically-linked dependencies other than the standard library (which is tightly coupled to the compiler) from source on every compilation, and this has been the case since the language's beginning. For an explanation of why C++'s need to preserve backwards compatibility for compiled code inhibits optimization, see: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2028r0.pdf https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p20... As for your other point, I think Rust will need smoother C++ interop than it currently has before it can displace C++, but there's hope. Here's the project I'm currently most optimistic about: https://github.com/google/crubit https://github.com/google/crubit
- chrsig 4y ago> Could you elaborate what I can do in C++ that I can't do in Rust (using unsafe, if I must)? Cuda :(
- chlorion 4y agoThere are still some things that C++ can do that Rust can't. A few of the larger and common examples would be, specialization, placement and variadic generics. At least variadic generics isn't really a performance thing, it's just an example of a rough edge you run into here and there. Specialization and placement can be pretty important for performance though!
- estebank 4y agoPlacement is not yet a language feature, but it can be expressed in a verbose manner and some have written macros for it.
- virtualritz 4y agoI was asking in the context of OPs claim. And that was regarding low level optimizations. I think only placement is a valid counterexample here but AFAIK there is support for Rust in that, if a bit elaborate in expression.
- shaklee3 4y agotemplate metaprogramming
- virtualritz 4y agoThe OP was talking about writing performance critical code in either language. Template metaprogramming is not a language feature that is needed when I write code that is close to the metal.
- shaklee3 4y agoit absolutely is. I do a lot of embedded programming, and tmp is used heavily in libraries to offload as much as possible to compile time.
- virtualritz 4y agoCompile time code generation can be done in many ways. Again, templates are not a feature that is part of the close-to-the-metal part of C++. What exactly does template metaprogramming in C++ allow you to do that you could not do in Rust? In the context of performance critical code? Besides, Rust has generics and macros. The latter are arguable as powerful (if almost as cumbersome to debug) as C++ templates. See also https://news.ycombinator.com/item?id=16283956 https://news.ycombinator.com/item?id=16283956
- kaba0 4y ago> I think the Industry needs at least one language, which does not constrain the developer in any way That’s not a thing you want from a programming language. What you can express is just as important as what you can’t.
- halpmeh 4y agoC is a way better language in that regard than C++.
- JonChesterfield 4y agoI think the Industry rather likes heavily constrained languages. There's a sense in which that's the point of a language - it's an abstraction that cannot be breached, which limits what you can ask the underlying machine to do. C++ won't let you define new types at runtime, but the advantage there is you know that the rest of your C++ codebase won't be doing that. It won't let you reflect on runtime data, but that means various changes you make to your program are undetectable outside of bounds (class or translation unit, sometimes shared library). Forth or assembly will let you do whatever the hardware is game for because either can emit raw bytes and then interpret them as machine code. The cost is you have, at best, conventions about what the rest of the codebase is doing. If you push too hard towards high performance in C++ the language lets you down. Aliasing rules mean data must be always atomic or never, and can't sometimes be an array of simd values. Or fno-strict-aliasing which costs you elsewhere. No control over calling conventions, instruction selection, register allocation or scheduling. On the bright side you can usually force partial evaluation well enough with template instantiations, but you can't have the inverse where you explicitly fold equal machine code implementations on different types. Plus trivial stuff like the embarrassment of unique_ptr imposing overhead if you pass it to a function. So C++ will get you within N% of optimal, most of the time, and that's usually good enough.