4 ms·
> 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,
by 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.
- nyanpasu64 5y agoFrom what I hear, making "intrusive linked lists which lend out &mut" sound is an unsolved problem: https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462 https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a...
- oconnor663 5y agoOh yeah for sure. Even if you make sure your lists don't have any cycles, it's also possible for a given object to be on multiple different lists. Sometimes that's the whole point of the intrusive approach. But if you're accessible from multiple lists, there's no way a list can meet the uniqueness requirements of &mut. I guess you have two options: 1. Make the whole list API unsafe, so that the only way to expose a safe API on top of it is to make the list an internal implementation detail. 2. Take the Rc<RefCell<T>> or Arc<Mutex<T>> approach, where each element effectively has its own lock. Of course in high-performance code this is usually too expensive, and in any code calling .borrow() or .lock() all over the place is annoying.