4 ms·
That's my take too. Idiomatic Rust is "safe" in the sense that... it disallows most nontrivial data structures. Even a doubly-linked list is impossible to get
by ajross 4y ago
That's my take too. Idiomatic Rust is "safe" in the sense that... it disallows most nontrivial data structures. Even a doubly-linked list is impossible to get through the borrow checker.
That's... not really that fatal. There's a lot of very useful code that can be written using only runtime-provided[1] containers and straightforward ownership trees.
But obviously the big problem is that for applications that do need to do non-trivial reference semantics, Rust doesn't really offer much. Applications with nonstandard allocator paradigms or weak-referenced caches or that need to do complicated graph management are mostly on their own in the world of unsafe.
And the somewhat more cynical point is: for applications that can easily fit within standard containers and standard allocation paradigms, C++ actually works really well already. Use your smart pointers. Use your containers. Follow the rules everyone tells you about not using bare new/malloc. And... it's basically just as safe, because anything beyond that would be unsafe in Rust too.
I don't dislike Rust, really. But the space between "Need to stay away from C++" and "Should probably just have written it in Go" seems to be getting smaller and not larger.
[1] Which in Rust, means "written using a ton of unsafe blocks".
- junofan 4y agoAgreed, having touched browser development circa Rust’s invention, I think there are some good ideas, but the execution fell flat over the years, orthogonality broke, and thus the utility is kind of limited. I’m more excited about some newer languages to be honest.
- noobermin 4y agoRandom question, doesn't this then defeat the whole purpose of using rust? Memory errors, that is, pointer issues are the primary source of errors that ownership in rust claims to fix. If the only way you can make nontrivial data structures is use "a ton of unsafe blocks," then you'll still likely get the memory errors, only now it's in an unsafe block...so, now everyone can just blame the unsafe block? This reminds me of those static provers which prove everything correct up until user input, you know, the place where an attacker will inject their payload.
- stjohnswarts 4y agoNot really, rust has an excellent build system (better than any c++ build system I've used), great community accepted libraries for just about everything integrated well into the build system, catches a lot more "move semantic" and ownership type errors that are easy to mess up in c++. There are other things, but for me it's the excellently maintained and integrated libraries and build system that are kind of a joy to use coming from c/c++ land.
- pitaj 4y agoIt's much easier to audit a ton of unsafe blocks beneath a safe abstraction layer than an entire codebase. And Rust provides very powerful tools like Miri which can catch pretty much any soundness issue if you have good enough test coverage.
- School-Cotton 4y ago> doesn't this then defeat the whole purpose of using rust? No because in practice you only need unsafe a tiny minority of the time, whereas C++ is always unsafe. Most programmers do not spend most of their time writing low-level cyclic data structure libraries. If they did, then indeed some of Rust’s advantages would be mitigated.
- lmm 4y ago> And the somewhat more cynical point is: for applications that can easily fit within standard containers and standard allocation paradigms, C++ actually works really well already. Use your smart pointers. Use your containers. Follow the rules everyone tells you about not using bare new/malloc. And... it's basically just as safe, because anything beyond that would be unsafe in Rust too. If you're perfectly attentive and constantly vigilant, maybe. In practice everyone thinks they're following the rules and everyone messes it up.
- einpoklum 4y ago> If you're perfectly attentive and constantly vigilant, maybe That used to be the case 10 or 15 years ago. Today it is much easier to "follow the rules", because: 1. You used to need to tread carefully to both follow them and do what you needed to; now you can do more complex things more easily. Example: In the past, you couldn't avoid new and free being strewn around your code. These days, you can avoid them entirely when not implementing a complex data structure of your own. 2. A decent version of "the rules" is basically explicit: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines 3. The standard library and other FOSS libraries do a lot of the rule-following for you (4. Compilers are more attentive and do more static checking.)
- Ygg2 4y ago> Even a doubly-linked list is impossible to get through the borrow checker. https://github.com/rust-lang/rust/blob/master/library/alloc/src/collections/linked_list.rs https://github.com/rust-lang/rust/blob/master/library/alloc/... It's part of standard lib. And you don't get more idiomatic than that.
- vasama 4y agoThat looks like an allocating container. Probably the most useful property of linked lists and other node based data structures is the ability to make them intrusive and to avoid dynamic allocation. In some domains you just don't have a runtime allocator available to you, so this library would be useless.
- Ygg2 4y ago> That looks like an allocating container Depends what you mean by that. It has 2 pointer to other nodes and element on the heap (EDIT: Not stack). It's bog standard double linked list. If you're looking for intrusive lists those a very niche data structure. https://docs.rs/intrusive-collections/latest/intrusive_collections/index.html https://docs.rs/intrusive-collections/latest/intrusive_colle...
- ajross 4y agoYes, and the pointer management is all done using unsafe blocks: https://github.com/rust-lang/rust/blob/master/library/alloc/src/collections/linked_list.rs#L164 https://github.com/rust-lang/rust/blob/master/library/alloc/...
- petre 4y ago> But the space between "Need to stay away from C++" and "Should probably just have written it in Go" seems to be getting smaller and not larger. We also have D which can use C/C++ libraries seamlessly.
- roca 4y ago> the big problem is that for applications that do need to do non-trivial reference semantics, Rust doesn't really offer much. For those applications you can almost always encapsulate the unsafe code in a data structure library with a safe interface, such that the library is a tiny fraction of the application code. And in many cases someone else already wrote that library. For graphs, for example, there is petgraph. > for applications that can easily fit within standard containers and standard allocation paradigms, C++ actually works really well already Not at all. For example it is still easy to corrupt memory using C++ references. Just the other day I discovered a nasty little bug involving absl::hash_map: someone had written "map[i] = map[j]" which is unsafe if the element at i does not already exist --- it can trigger a rehash, invalidating the reference obtained by map[j].
- scoutt 4y ago> For those applications you can almost always encapsulate the unsafe code in a data structure library with a safe interface, such that the library is a tiny fraction of the application code. This is something I never understood. Given that a Rust application is running on top of an OS making OS calls... or uses a huge library like FFMPEG... The only safe portion is like the 1% of code being executed. "such that the library is a tiny fraction of the application code" I mean, your Rust app uses sockets to pull a video and save it with FFMPEG. The quantity of code executed to do that is like 99% unsafe and 1% safe. Right? What are we talking about here? I can still find a bug in FFMPEG and ROP-exploit some code in the safe 1%, right? The same languages and code Rust evangelists (and Mark Russinovich if not one) are blaming is the only code that makes Rust do something meaninful. Don't you agree?
- selfmodruntime 4y agoBoth doubly linked lists and cyclic graphs can be written in a way that satisfies the borrow checker. If not already existing in the STL, there is a crate for it 99% of the time.
- pkolaczk 4y ago> for applications that can easily fit within standard containers and standard allocation paradigms, C++ actually works really well already. Use your smart pointers. Use your containers. Follow the rules everyone tells you about not using bare new/malloc. And... it's basically just as safe This is "don't write bugs, lol, and you're safe" argument. It has been demonstrated many, many times on HN, that you can cause UB in C++ using just high-level standard containers and smart pointers, without doing anything suspicious with raw pointers. Bugs of that kind are virtually impossible to detect during a typical code-review. So it is not "just as safe". It is tad safer than C, but nowhere near Rust. > But the space between "Need to stay away from C++" and "Should probably just have written it in Go" seems to be getting smaller and not larger. That might sound as a rant, but IMHO the designers of Go sadly made some odd choices in areas unrelated to memory management, which put me off from Go. Go is not just Rust with borrow checker replaced by tracing GC. It is a completely different language, with a much different "feel": * some syntax choices that seem to have no justification other than "we wanted to make it look different than other languages" (ok, one can get used to it) * no sum types / unions / enums * code generation / copy pasting instead of proper macros * error handling not really better than in C (caused by no sum types) * visibility controlled by character case (so when you want to unprivate sth, you have to find-replace all occurrences; I guess this stupid idea originates from Python) * unused stuff is hard error (terrible for prototyping) * you don't need generics, but we've just added them anyways ;d * no tools for controlling mutability/immutability/sharing (which is still useful in GCed languages) * data races possible, accidental mutable sharing possible * no RAII for deterministic destruction of non-memory resources (`defer` doesn't even come close) * package/dependency management IMHO subpar compared to cargo When trying to learn Go, I had exactly same feeling as when I first learned Java (after C++) - that the authors of the language designed it for people less capable than the creators themselves. So I didn't like it.
- survirtual 4y agoThe thing is, once you get it right in Rust it just works very, very well — and it generally works multithreaded with little additional effort. The language causes you to rethink datastructures that were largely created in a single-core context. You can think up equivalent functionality that satisfies the Rust borrow checker but then also lets you trivially parallelize via something like Rayon. Rc or Arc work for a double LL if you insist on pointers, or a wrapped owned vector with integer based references if you don’t want the overhead of reference counting. But to say “Rust doesn’t offer X” is just not true; you can build anything but it just takes more work and thought, and it is always worth it given the performance and bug classes automatically eliminated by that extra effort. And with unsafe code, you get bare metal access without the rules.
- UncleMeat 4y agoModern C++ happily introduces nightmare bugs. Banning "new" from your codebase does not prevent problems. You can still happily have UAF through references. The adoption of string_view has made this super clear. Arithmetic is still very error prone, with confusing promotion rules and little protection for overflows. Pointer arithmetic is still common - yes even if are using typical containers. What do you think is happening underneath the hood when you increment an iterator? Oh, and iterator invalidation remains a fun way of ending up with unsafe code even if everything looks totally fine.