4 ms·
I totally get what you mean. It is surprising. And because it's surprising, a lot of beginners dive right into it without realizing what they're getting into, a
by oconnor663 5y ago
I totally get what you mean. It is surprising. And because it's surprising, a lot of beginners dive right into it without realizing what they're getting into, and many wind up frustrated. It can feel like Rust is throwing up a bunch of pointless roadblocks at you for no reason. But here's how I'd summarize the reasoning behind it:
In most languages, there are few or no restrictions on which objects are allowed to point to which other objects. In garbage collected languages (Java/Python/JS/Ruby/Go/etc), this is because the language runtime keeps objects alive for you as long as you hold pointers to them. In not-memory-safe native languages (C/C++/Zig/etc), this is because the language holds you responsible for avoiding use-after-free and other issues. These two groups of languages are quite different from each other, but one thing they have in common is that you'll never see an error message that says "Object A isn't allowed to point to object B."
This is one of the things that makes Rust different from both of groups above. Rust is not garbage collected, but it is memory-safe. To make this work, the Rust compiler has strict rules around when you're allowed to retain a pointer to some object and when you're not. These rules have a substantial learning curve, partly because they can be tricky, but also because they're new and unfamiliar to most programmers.
So the reason linked lists are more difficult to write in Rust is that they're pointer-heavy data structures. Implementing a linked list runs you straight into the pointer rules that make Rust different and sometimes tricky. To make everything work, you either need to spend some time studying those rules first, or you need a teacher who can point you in the right direction and answer your questions as you go.
Anyway, if you're curious to learn more, there are several excellent books available, including an official one that we often just call The Book: https://doc.rust-lang.org/book/ https://doc.rust-lang.org/book/
- anamax 5y agoI know why lists are hard to do with Rust's guarantees, and can even understand why a given memory safety validator might have trouble with them. My point is that I expect a language eco-system to make it possible to do "easy" things without much trouble. As the saying goes, a ship is safe in harbor, but that's not what ships are for. Safety is a desirable property but it is not why (most of us) write programs. (I'm reminded of early Haskell, where programs could be guaranteed to produce the correct output but no guarantee that they'd produce output.)
- oconnor663 5y ago> I expect a language eco-system to make it possible to do "easy" things without much trouble. I think a lot depends on what we mean by "trouble". For example, here's a basic implementation of an unsafe doubly-linked list with a safe API: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=43e25c783f2d5ab514c78b6692517aa6 https://play.rust-lang.org/?version=stable&mode=debug&editio... There are only a few things in there that jump out to me as inconvenient: - We have to write `unsafe` blocks in several places. - When we dereference raw pointers, we have to write things like `(*self.head).next`, with more parentheses and stars than usual. - We need to add PhantomData<T> members to our iterator types, so that the Rust compiler knows whether their lifetimes are covariant vs invariant. None of those inconveniences adds very much in terms of lines of code. And of course, some of them are inconvenient by choice. Rust wants to be extra clear when we're doing unsafe things like dereferencing a raw pointer. However, there are several places in this example where knowing the right thing to do is a serious challenge: - The PhantomData<T> thing from above counts as one of these too, although a compiler message will point you to it if you forget. - We need to store raw pointers to nodes rather than shared or mutable references to nodes. - We allocate memory with Box::leak(Box::new(...)). - We free memory with Box::from_raw(...) and end-of-scope destruction. - We need to add an explicit Drop implementation to avoid leaking memory. All of those are intermediate or advanced Rust concepts, and it doesn't make sense to try to teach them to beginners. It's also easy to get them wrong, much like malloc() and free() and constructors and destructors are easy to get wrong in C and C++. But once you understand these concepts and know how to use them, I wouldn't call them inconvenient per se. It's not clear to me that a helper library from the ecosystem would be able to help much here. Maybe we could find a way to reduce repitition in the implementations of the shared and mutable iterators? But for the most part, the challenge here is correctness, not repetition. Of course, what the ecosystem can do for us is provide a ready-made implementation of the whole container, so that we don't have to do any of this ourselves: https://doc.rust-lang.org/std/collections/struct.LinkedList.html https://doc.rust-lang.org/std/collections/struct.LinkedList....
- anamax 5y agoStandard library that takes care of all of the hassle can be good enough.