5 ms·
This article only covers the code. For the projects I work on (mostly C++ and/or C#), about 50% of decisions I make while coding is about data, not code. IMO,
by Const-me 10y ago
This article only covers the code. For the projects I work on (mostly C++ and/or C#), about 50% of decisions I make while coding is about data, not code.
IMO, Rust just not good enough for creating complex specialized data structures.
Even a trivial tree is hard to implement in Rust, and the implementation will be severely limited: https://github.com/SimonSapin/rust-forest https://github.com/SimonSapin/rust-forest
D surely has it’s problems as well (in short, I think the language itself is very good, but its runtime and libraries still need a lot of work), but at least it’s good for complex data structures.
- steveklabnik 10y agoYes, internals of data structures often need unsafe. At least with Cargo, and generics, you can implement them once, and then share them amongst projects. It's also why we have a fair number of data structures in the standard library; they will be audited a bit more heavily this way.
- bluejekyll 10y agounsafe in Rust == telling the compiler I know what I'm doing here, so eff off. Other languages do this too, for instance in Java you can override final with reflection. I lost my mind, both good and bad, when I discovered this.
- steveklabnik 10y agoBut only in a controlled way. Unsafe doesn't change the semantics of existing code, it only adds the ability to do a few extra things. So you're still going to get a lot of safety checks, even within an unsafe block. You still have to be careful, but it's not no-holds-barred.
- bluejekyll 10y agoYeah. I've read your chapter on this repeatedly, and then the Rustinomicon, both hugely useful, but so far all I've done is use of for Rust -> C bindings and vice versa. It's taken me a year to feel good about my Rust code, I'm still getting a handle on unsafe though.
- Const-me 10y agoEven using unsafe, are you sure I can implement e.g. efficient linked list data structure? By “efficient” I mean I want to split my list in two for O(1) complexity, like other programming languages do.
- frankmcsherry 10y agoHere is a version (https://is.gd/bFCpMq https://is.gd/bFCpMq) that might be instructive. It is based on a simple ref count cycle of structs like: pub struct ListNode<T> { data: T, prev: Weak<RefCell<ListNode<T>>>, next: Rc<RefCell<ListNode<T>>>, } The code linked above probably has bugs (for sure with singleton lists), but I hope you get the idea. It allows you to splice lists in and out in constant time. Also, the code doesn't use `unsafe`. Now, you would need to use `unsafe` somewhere to actually create a list, in order to initialize the fields (or you could change the type of `next` to be `Option<...>`). However, the use of `unsafe` would be limited to that method. This tries to get at the difference between (i) occasionally using `unsafe` and (ii) having no safety guarantees at all. There are probably lots of other pain points that you can bring up, and I'm not trying to say this should be good enough, but I thought it might help explain what is possible.
- steveklabnik 10y agoUsing unsafe, you can do anything C can do.
- kibwen 10y agoIn what way are data structures limited? Things like e.g. efficient intrusive data structures are difficult to implement in Rust without resorting to `unsafe`, but even if you do give in and start using unsafe code you've got no less safety than C++, since all C++ code is unsafe by Rust's standards anyway.
- Const-me 10y agoIn C++, I can use pointers (or their equivalents like STL’s iterators or ATL’s positions) to combine simple built-in data structures into complex data structures to suit my needs. For example, a vector that keeps some items + hash map that implements an index over that vector. Or some container that keeps some items + a linked list to track and evict the least recently used item from that container. Rust is just too safe for that. That might be technically doable, but harder to implement, and harder to use. This certainly applies to Rust’s built-in data structures as well. See this example, the problem is trivial to solve in most languages: https://www.reddit.com/r/rust/comments/3cz71e/holding_a_mutable_ref_to_item_in_container_blocks/ https://www.reddit.com/r/rust/comments/3cz71e/holding_a_muta...
- kibwen 10y agoI can believe that such a data structure would be harder to use, since if you bubble the unsafety upwards in your API then you'll force your users to use the `unsafe` keyword. But I'm afraid I don't see how this implies that data structures would be limited or harder to implement: raw pointers exist in Rust, and they're exactly as powerful and unrestricted as they are in C++. The only way it would be harder would be if you were trying to use references to ensure safety guarantees about your program above and beyond what C++ is capable of guaranteeing (i.e. doing more than what C++ does). > See this example, the problem is trivial to solve in most > languages I disagree. That proposed code (again using references which provide guarantees that no other language provides) is failing because Rust statically prevents data races, which is not only non-trivial to solve in most languages, but actually impossible to solve in general in most languages. The way around this is to, again, regress to what other languages do and use raw pointers directly.
- cfallin 10y agoI thought so too at first, but the thing about the limits imposed by the borrow rules is that they completely avoid the bugs that, in my experience, take the longest to track down in e.g. C++. It's the pain-up-front vs. more-pain-during-debugging argument. I've slowly been converted to the former camp from the latter as I write Rust full-time. The experience is just more... deterministic and pleasant. (Have you ever accidentally kept a pointer to an element of a std::vector too long and then caused the vector to reallocate?) If you want you can always use raw pointers and dereference them in unsafe {} blocks, and even provide a safe API (return borrows with appropriate lifetimes) around this. You'll find plenty of common patterns in the standard library and on crates.io too.
- arbre 10y agoI agree about this. I wanted to give rust a go and tried to implement a graph for a DFA. I realized it was a pain. Even in general I find rust super hard to use with the references lifetimes etc. I don't understand the buzz going around rust when it's so hard to use in practice. I am frustrated with new languages. I agree that C++ is old and needs a more modern replacement but: - Go is weird with no templates and no functional programming. - Nim comes close to my dream language but has a GC and some very awkward features like globally imported namespaces. If I had to start a new project I would probably pick Nim or C++.
- nimmer 10y agoGC in Nim is optional and it's run inline during memory allocation only. No background threads. http://nim-lang.org/docs/gc.html http://nim-lang.org/docs/gc.html
- frankmcsherry 10y ago> Even a trivial tree is hard to implement in Rust, and the implementation will be severely limited: https://github.com/SimonSapin/rust-forest https://github.com/SimonSapin/rust-forest I mean, you haven't actually pointed at a trivial tree implementation. The requirement that children be able to navigate to siblings is the issue here (complicating unique ownership by parents). You could totally do a "trivial tree" as struct Tree<T> { payload: T, children: Vec<Tree<T>>, } which your link even says. You would need a handle to the parent to surf around through siblings, is the limitation. It's not really clear (to me) that the rctree is really all that complicated; the Rc stuff is there to clarify ownership: for how long should allocated memory stick around. It might just read weirdly due to familiarity, in the same way that D might weird me out.