10 ms·
It's the only language existing that is memory safe and provides zero cost abstraction to the CPU capabilities (i.e. no mandatory GC, values don't need to be bo
by devit 5y ago
It's the only language existing that is memory safe and provides zero cost abstraction to the CPU capabilities (i.e. no mandatory GC, values don't need to be boxed, ability to mutate memory).
The only limitation that may not eventually be removed is the lack of dependent types (although you can kind of emulate them by lifting terms to the type level and using lifetimes to represent variables).
C++ is not memory safe, Lisp has no type system and Haskell has GC, all values are boxed in a closure and it cannot mutate memory safely, so none of these languages are even in contention.
- benreesman 5y agoC++ compiled by clang-12, with clang-tidy and cppcheck turned all the way up, with everything owned by a std::unique_ptr, run through ASAN, UBSAN, TSAN etc (which seems to build about as fast as Rust) lets what by that Rust catches? I’m not being sarcastic, in all earnestness educate me!
- fluffything 5y agoDoes that warn if you do: vector<int> v{1,2,3,4}; for (auto&& i: v) v.push_back(i);
- benreesman 5y agoIn my little toy build I set up no, it doesn't warn me that smashing a data structure while iterating over it at the same time gives UB. I'm 50/50 on if there's a flag for that, don't actually know. Again, in the spirit of becoming more educated, what's the equivalent Rust code? I have `rustc`/`rustup` on my box so I can run that too.
- paavohtl 5y agoThis should be about equivalent. It does not compile, as expected. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2ebc047ced2efbe897f0a4011e64c22a https://play.rust-lang.org/?version=stable&mode=debug&editio...
- gravypod 5y agoAnother fun feature of rustc is you can execute this command: rustc --explain E0502 It gives you a small example of what the error is, explains it, and tells you how to fix it. This is a very nice feature for new people (like myself).
- benreesman 5y agoThat's definitely useful, and I'm not completely mystified by the notion that simultaneous borrowing of something as both mutable and immutable is something that Rust watches for. In C++ you (usually) do this with `const`, which is fairly low friction, very statically analyzable, and I'm a little unclear what bug hardcore borrow-checker static stops me or my colleagues from making that `const` can't catch?
- adwn 5y ago> In C++ you (usually) do this with `const`, which is fairly low friction, very statically analyzable, and I'm a little unclear what bug hardcore borrow-checker static stops me or my colleagues from making that `const` can't catch? Rust catches the bug where you forget to use `const`, because the C++ compiler doesn't force you to use it. Your argument boils down to "I don't need Rust's borrow checker, I just have to remember to use a dozen tricks, follow various patterns and guidelines, run several static analyzers and runtime checks, and I'm almost there". This is not meant as an attack against you. I used to program in C++, and I'm mostly using Rust now. The borrow checker frees up those brain resources which had to keep all those tricks, patterns, and guidelines in mind.
- deleted 5y ago[deleted]
- electrograv 5y agoConst in C++ is entirely unlike immutability in Rust. Even ignoring the existence of “const_cast”, C++ const is vastly weaker than Rust immutability in several extremely fundamental ways that should be immediately obvious if you’re a C++ professional with even a surface level introductory understanding of Rust. At this point you honestly should just take some time to learn a bit of Rust, and it will likely all make sense. If not, it’s possible you’re relatively amateur C++ developer (no shame in that) lacking experience of the many ways in which C++ const falls very short. But to directly answer your question, suppose I have a “struct FloatArrayView { float* data; size_t size; };” Of course this is a toy example, but humor me for now and consider the following: 1. Without excessive custom effort (e.g. without manually unwrapping/wrapping the internal pointer), how do I pass it to a function in C++ such that the data pointer within becomes const from the perspective of that function (i.e. you’ll get a compile error if you try to write to it?) Hint: It’s very complex to do this in C++, vs trivially easy in Rust. And no, passing as const or const reference to “FloatArrayView” does NOT work. The only solution in C++ for complex composite types operating with “by-reference semantics” is ugly, complex, and horribly error prone to maintain correctly. For a more concrete example, consider how “unique_ptr” works with the constness of the value it holds. A “const unique_ptr<T>” is NOT a unique pointer to const T. A “unique_ptr<const T>” is, but the relationship and safe conversions between these are not simple or easy to implement or even use in many cases. It gets even worse when you need to implement a “reference semantics” type like this that is inappropriate to be a templated type, or contains a variety of internal references unrelated to the template arguments. 2. Now suppose I solve #1 and pass this into some class method, which then stores a copy of the const reference for later. But I as the caller have no way of knowing this. So after that method returns, I later proceed to modify the data via my non-const reference (which is a completely valid operation), but this violates the previous method’s assumption that the data was immutable (will never change). This creates an incredibly dangerous situation where later reads from that stored const reference to data (that was assumed to be immutable) is actually going to actually yield unpredictably changing results. Const is not immutable. Question: How do you make it so C++ guarantees that passed reference is truly immutable and will never be mutated so long as the reference exists, and enforce this guarantee at compile time? In Rust this is easy (in fact, it’s the default). In C++, it is impossible. The closest you can get in C++ are runtime checks, but that’s nowhere near as good as compile time checks. Edit: Removed a bunch of perhaps unnecessary extra C++ trivia which I’ll save for later :)
- namibj 5y agolet mut v = vec![1,2,3,4]; for i in v.iter() { v.push(i);}; And that doesn't work, because the v.iter() is sugar for Vec::iter(&v), while the v.push(i) is sugar for Vec::push(&mut v, i) . I think it'd use deref coercion on the i, as Vec::iter(&v) gives you `i: &isize`. If this wasn't ints (or other `Copy` types, for that matter), you'd need to use .into_iter() to consume the Vec and get ownership of the entries while iterating, or use `.push(i.clone())` because `Vec<T>::push(&mut self, T)` is the signature, and you can only go automatically from `&T` to `T` for `Copy` types. Actually, it _may_ even need `v.push(*i)`, thinking about it. Try on https://play.rust-lang.org https://play.rust-lang.org
- benreesman 5y agoSo I don't have the same intuition for desugaring `vec!` that I do for desugaring `for (auto&& blah : blarg)`, but in either case if you desugar it the problem becomes a lot more clear. The Rust borrow checker errors I'm sure become second nature just like the C++ template instantiation ones do, but that is faint praise. To get some inscrutable C++ template instantiation error you have to mess with templates, and that's for people who know what they're doing. In Rust it seems like the borrow checker isn't for pros, it's C++-template complexity that you need to get basic shit done. C++ is actually a pretty gradually-typed language, and I'm in general a fan of gradual typing. I don't mind that some people prefer BDSM-style typing, but IMHO that goes with GC e.g. Haskell a lot better than it does with trying to print something out or copy it or whatever.
- zxzax 5y agoIt's not the same as C++ template errors. This is something that will directly cause a segfault in your code, that the Rust compiler is able to catch; AFAIK no C++ compiler would be able to catch that.
- kd5bjo 5y agoThe problem here is that you’re trying to iterate over `v` and modify it at the same time. The usual fix is to first decide the changes that should be made, and then apply them after the loop: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f466f888f95c2cf3ccbeffbc10f76b7d https://play.rust-lang.org/?version=stable&mode=debug&editio... Alternatively, you can loop over indices instead of values, which doesn’t require the loop to maintain a reference to `v` across iterations: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a9d565552c4a8b37b123c42101e91239 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- divs1210 5y agojust code in assembly then. Batteries included with safe defaults is a BIG feature. Legacy C/C++ code is littered with so many memory related bugs that big companies are funding people to rewrite essential unix utils in Rust.
- benreesman 5y agoNo argument that legacy C/C++ code is Swiss cheese on 1980s memory bugs in a lot of cases. Modern C++ vs. modern Rust. Why is modern Rust better? Legacy C++ can be transformed into modern C++ is a semi-mechanical, semi-you-emply-someone-good-at-emacs way, at a cost in time, money, and risk way less than empty editor.
- CJefferson 5y agoThere are, for me, two problems with ASAN and friends: 1) it requires a later execution, rather than your code failing to even compile. 2) it requires excellent and complete tests, probably with quality fuzzing, as often the corner cases (like hiting buffer size limit) is where problems occur. For me, the argument for Rust is exactly that it let me (mostly) get rid of ASAN and UBSAN and valgrind and friends, which I consider required for C++. The other big advantage of Rust is package management, with I feel C and C++ are awful at, particularly if you want to release for Linux, Mac and Windows.
- benreesman 5y agoThe package management point is completely on point. CMake blows and mixing in autotools makes it worse. I think I consider the need to run under ASAN in a configuration that gets code coverage a feature rather than a bug. It's stupid monkey-brain stuff, but the forcing function of needing to be able to drive your critical path at will in my experience leads to better outcomes.
- tialaramex 5y agoComposability bites you badly here. You need that ASAN run, with full coverage, for the entire C++ system, any time any part of it changes, even if the changed element seems irrelevant and clearly safe, the way C++ is defined that doesn't mean it didn't make the whole system unsafe. But since Rust's Memory Safety guarantees apply to components, the composition of those components also has Memory Safety by definition. If you have one guy writing incredibly scary to-the-metal unsafe Rust to squeeze out 1% more performance from a system that's costing 85% of your company's burn rate, you can put that tiny component through ASAN hell, looking for any possible cracks in its Memory Safety with all sorts of crazy input states. But the sixteen other people in the team writing stuff like yet-another REST JSON handler entirely in Safe Rust don't need that effort and when you compose all these pieces together to build the actual product, you don't need ASAN again, you checked that the scary bit was safe and you're done.
- dhosek 5y agoOh God, CMake and dependencies are at least 80% of why I decided to learn Rust. I decided to give a shot to C++ back in November/December for a project I'm working on and it was ridiculously painful to do something like set up a simple library with unit tests using CMake, especially coming from writing Java in my day job where it's trivial to do this sort of thing. Starting from scratch with Rust about a month and a half ago, I've been more productive in that month and a half than I was in five months of banging against CMake and trying to figure out how to bring in external dependencies.
- paavohtl 5y agoIt's not that Rust catches these issues like a sanitizer or linter would, it's that they can't exist by design. The compiler is aware of potential type-, lifetime- and ownership safety issues, and you don't need to run any code to figure out if your code is memory safe or not; static analysis is enough. Also, even if Rust only caught by itself what Clang and half a dozen analyzers would catch, you also get a really nice language as a bonus. There is much more to Rust than just safety.
- benreesman 5y agoSo, I'm not really sure how the distinction between `rustc` and the government-issue `clang` chain really matters unless one is a lot faster, and they're both really slow. The by-design thing I get for like something with a port open, but as a default? It's been a few months but the last time I was trying to hack a Rust project printing stuff out hit the borrow checker. I can live without that.
- paavohtl 5y agoYou've mentioned the printing issue a couple of times in this thread, and it sounds pretty weird to me as a someone who has used Rust for a couple of years. Without knowing the specifics I can only assure you that if you know the basics of the language, you'll have no issues printing whatever you want whenever you want. Rust is admittedly a language you can't just jump into without spending some time learning the fundamentals. If you try to do anything non-trivial without grokking the basics of ownership and borrowing you are bound to run into frustrating issues.
- db48x 5y agoHe probably tried to do dbg!(foo), which takes foo by ownership and consumes it. Doing dbg!(&foo) will accomplish the same thing (printing foo along with the line number and file name), but because it only uses a reference to the foo, it won’t consume the original. This is perfectly sound and logical once you know the explanation, but it is pretty weird to a newcomer.
- MaxBarraclough 5y agoWhat you've described is a way of catching some memory-safety problems in C++ codebases. There's no easy way to catch them all. Even Chromium is riddled with memory-safety issues that result in serious security vulnerabilities. [0][1] We don't have a practical way of writing large and complex C++ codebases that are entirely free of memory-safety issues. I don't know enough about Rust to comment on whether it does a better job than C++ on reference cycles, but my suspicion is that it does. [0] https://www.chromium.org/Home/chromium-security/memory-safety https://www.chromium.org/Home/chromium-security/memory-safet... [1] Related discussion: https://news.ycombinator.com/item?id=26861273 https://news.ycombinator.com/item?id=26861273
- benreesman 5y agoChromium is a *way* legacy codebase, I think WebKit goes back to like Konqueror or something? Chromium is a very weird example to cite for modern C++ vs. modern Rust memory safety. AFAIK avionics software is still largely written in Ada, because it won't let you fuck up meters vs feet type stuff. And if someone said: "Rust has a slam-dunk niche: we're going to crank static analysis past helpful to downright intrusive because sshd simply can't buffer overflow", I'd be like, yeah, ok. But at the time I stopped using it, Alacritty couldn't handle meta keys on Big Sur, and I wanted to fix that, so I spent a weekend or two that I really couldn't spare trying to unfuck it, but between `print` not being obvious (because someone had already borrowed the thing I wanted to print out) and the build being slower than C++ I timed out.
- cvwright 5y agoOTOH Chrome has one of the best teams in the world working on it, funded by one of the richest companies in the world, with the best tools. And they take security very seriously. If they can’t get it right, who can?
- MaxBarraclough 5y agoA good point, but I imagine benreesman's counterpoint would be that things might be different if Chromium were written entirely in modern C++, strictly following modern best practices. My suspicion is that this is too optimistic, but I can't really substantiate it. What would be a good security-sensitive modern C++ codebase, ideally from a high-profile source like Google, to compare against?
- fnord123 5y agoCool that you use all those tools. Wouldn't it be cool to be able to depend on libraries knowing they all use those settings? And you can depend on libraries by putting a single line in a toml file. I mean C++ dependency management is so bad there is a concept of a header only library so you just have to copy the file into your project with no support for managing versions. C++ dependency management is so bad people use the OS installed dependcies because they struggle to set up a build environment otherwise. What I'm saying is that C++ is pretty good and toe to toe C++ with all the tools you describe and Rust are fairly comparable. But with Rust the whole ecosystem uses it so the network effects make things much better. And it's snowballing.
- benreesman 5y agoYou make a really good point about C++ headers. Textual inclusion of globs of bytes into translation units is 1970s legacy stuff that totally fucks up reasonable build times, and for all the talk about C++20 modules they haven't delivered yet. I actually think this is one of the things that Rust could crush C++ on, because build times are becoming the whole show on big C++ projects. It's super weird to me that C++, with this whacky 1970s constraint that totally fucks up modularity, still builds neck-and-neck or better with Rust. In principle Rust could do *way* better on this, and that is a feature that might get me to consider Rust seriously, no matter how stupid I think the borrow checker is. But Rust still compiles dog slow...
- conradludgate 5y agoI find if you're aware about how you define your modules, incremental compiles are usually pretty quick. Yes a complete build can take a while but tools like sccache[0] can help with that in CI pipelines and when getting a new dev environment up. [0]: https://github.com/mozilla/sccache https://github.com/mozilla/sccache
- fnord123 5y ago>It's super weird to me that C++, with this whacky 1970s constraint that totally fucks up modularity, still builds neck-and-neck or better with Rust. Not so fast! The compilation and linking is neck and neck with Rust so you can have an ELF in the same amount of time. But then you have to run all the C++ tools you mentioned to have parity with what the Rust compiler is doing. > for all the talk about C++20 modules they haven't delivered yet. Even when they're delivered, will all the libraries you depend on use them? >In principle Rust could do way better on this, and that is a feature that might get me to consider Rust seriously, no matter how stupid I think the borrow checker is. But Rust still compiles dog slow... The team that built the Borrow Checker deserves the Turing Award. It is an outstanding bit of computer science and engineering. I'm surprised that you think it's stupid. It used to be dog slow but it's gotten very fast in the past two years. Another achievement. >In principle Rust could do way better on this, and that is a feature that might get me to consider Rust seriously Modules? Your take away from all this is that modules are the killer feature of Rust and that the Borrow Checker is stupid? I call shenanigans. You are an elaborate troll. Well done for getting two responses out of me.
- imtringued 5y agoIsn't that a complete drag? Needing to install a dozen different tools just to do something basic as maintain security? There is no way you can hold every C++ project up to this standard. If you asked me to write software in C++ I would forget to use half the tools you listed and your list isn't even complete to begin with! Of course this is not an answer to your comment. Even if you use std::unique_ptr you can still run into use after free bugs. std::unique_ptr is just heap allocation with a "stack allocation style" lifetime. You can still run into use after free by putting a reference to stack allocated data (this applies to std::unique_ptr as well) into a struct or global variable that outlives the stack allocated data. std::unique_ptr isn't really meant to solve use after free, it exists to ensure that heap allocated data is properly freed eventually. It saves you the discipline to match every new with a free but nothing more. This is something that you can only solve with a borrow checker. If you were to add the ability to detect these types of problems in C++ then you would have essentially added a borrow checker to C++.
- andrewaylett 5y agoThis stack would catch _most_ problems, but not necessarily at compile time. It's unsound, because it's still possible to write data races. And it's possible that two components, each on their own correct, combine to make something that isn't correct. Rust's borrow checker makes it impossible to write code containing a data race. It does this by only accepting a subset of possible safe programs, but this is (or at least may be) a reasonable trade-off due to the guarantee you get. It's not that you can't write safe code in C++, but you're always at the mercy of your validation to try to have confidence that you've actually managed to do what you set out to do. While Rust obviously doesn't preclude bugs at all, but it does enforce that you _can't_ write a certain class of previously-prevalent bugs. I think this blog post from Mozilla does a good job of what that means in practice: https://blog.rust-lang.org/2017/11/14/Fearless-Concurrency-In-Firefox-Quantum.html https://blog.rust-lang.org/2017/11/14/Fearless-Concurrency-I...
- jandrewrogers 5y ago> It does this by only accepting a subset of possible safe programs, but this is (or at least may be) a reasonable trade-off due to the guarantee you get. I think this is correct but would expand on the nature of the tradeoff. Rust only accepts a subset of safe programs, and can express any program in the abstract, but that subset may not contain the optimal safe program. In some cases, differences in practical performance between the expressible safe program and the optimal safe program can be quite large. Modern C++ cuts a tradeoff along a different dimension: it will accept unsafe rubbish but the optimal safe program is also expressible in virtually all cases. C++ has large niches where it can express optimal safe programs, and where this optimality is important, that Rust cannot. Obviously it is incumbent on the programmer to write safe code in C++, but the flexible type system is more capable of enforcing safety than I think people expect, particularly in recent versions of C++.
- andrewaylett 5y agoAgreed. But there's optimal and there's "optimal" -- by restricting the developer, we open up more opportunities for the compiler to optimise the code (for all that Rust isn't necessarily able to take advantage of these opportunities yet) and a judicious use of `unsafe` may even help us to build an abstraction that gives us the optimal program anyway. Crucially, code that uses unsafe is still no less safe than ordinary C++ code. From my days as a compiler engineer, I definitely prefer to write clear code and fix the compiler rather than try to convince the compiler to produce the right result by mangling code. And to build my control logic as an abstraction, even if it's only going to be used in one place, so it can be verified separately from the business logic it drives.
- oconnor663 5y agoASan, UBSan, etc. are absolutely life-changing, no doubt about it. But there's a pretty big difference between catching things statically, and catching them at runtime. If you have an issue that, say, only triggers on odd-numbered Tuesdays on Windows 7, your tests suite is probably going to miss it, and ASan isn't going to be able to help you. Here's an example from one of Herb Sutter's talks, which I refer to frequently. He describes an issue that can come up with shared_ptr in reentrant/callbacky code, and the punchline is that you have to be careful never to dereference a shared_ptr that might be aliased: https://youtu.be/xnqTKD8uD64?t=1380 https://youtu.be/xnqTKD8uD64?t=1380 I also just wrote a giant example of my own :) in reply to your toplevel comment. https://news.ycombinator.com/item?id=27168368 https://news.ycombinator.com/item?id=27168368. TSan will be pretty good at catching some of the simple variants of this, like where you totally forget to take a lock. But it's going to have a hard time with cases where you "stash" a reference longer than you should, because later uses of the reference might appear safe for a while yet become unsafe down the road with unrelated code or timing changes. Another issue with ASan and UBSan, is that every application has to run them (and maintain its own test suite comprehensive enough to make them useful). With static checks, a memory-safe library is memory-safe for everyone, even for applications that aren't particularly careful with their tests. The library author is able to express their safety requirements through the language, and all callers have to respect those requirements all the time.
- Too 5y agoEverything owned by unique_ptr is not realistic, some times you need shared state. Rust provides a safer alternative to references if you don't want to go all shared_ptr. Even Rc becomes better than shared_ptr due to single mutating owner limit, especially for multithreading. Even with clang-tidy turned to 11 there are still many other UB-landmines lurking not only related to memory ownership and you'd be fighting the compiler even more over contradicting requirements or opinions. All the tooling you've just listed adds build complexity, on top of a build system that is already an abomination of makefiles, CMakeLists and lack of package manager. Let's not forget all the header files duplicating half of your implementation.
- deleted 5y ago[deleted]