What have you had to unlearn as your Rust skills have developed?


#1

I am wondering what people find themselves changing in their code when they start to write idomatic Rust. There’s a certain difference between translating another language (writing C++/Ruby/… in Rust) to feeling like you’re writing rustic Rust.

What’s your background and what have you learned?


#2

I had to learn to stop fixing bugs and start eliminating them once and for all, because not only is the typesystem powerful enough to create abstractions that enforce you to correctly use an API, when the API becomes convoluted enough, you can just write a lint to go along with the documentation.

I learned to trust the compiler and clippy to fix the small bugs, so I can concentrate on the design. I think I’m actually more careless when writing Rust than when writing C++ because of that. If that’s a good thing or a bad thing is still an open question.


#3

The gold thing is that you don’t need to care as much when writing Rust, because rustc (and clippy) has your back.


#4

I had to unlearn that dependencies are hell. (Coming from a C++ background.)


#5

I had to unlearn using exceptions for all error handling (coming from C++, Python, C#, etc). Rust’s ergonomic, yet explicit, (Result, ? operator) return based error handling strikes a really neat balance that I hadn’t found in previous languages.

My experience doing return based error handling in C, for example, was not pleasant.


#6

I’m very used to OOP with interfaces, and I’m probably overusing traits emulating such design patterns.

And I’m probably under-utilizing some other Rust features:

  • Closures. They exist! And are 0 cost!
  • Custom enums with data. I tend to use Option<>, because I still think in nullable pointers (struct {foo: Option, bar: Option, state: enum FooBarBaz} is what I’d need to do in C and alike, instead of enum {Foo(data), Bar(data), Baz}.

I miss C99 variable-length arrays. Rust’s arrays are totally useless :frowning:

And I miss OpenMP. It’s tuned for very C-like mutable stateful loops. Rust’s iterators require completely different approach, and I still haven’t figured out how to operate well on 2d arrays with them.


#7

Have you found the performance of Vec<T> to be lacking?


#8

convenient type.[quote=“timClicks, post:7, topic:9221”]
Have you found the performance of Vec<T> to be lacking?
[/quote]

In general no, but for things that VLAs are used for it doesn’t even seem appropriate, e.g.: int tmp[image->channels]. To me that’s a bit clearer and safer than tmp[MAX_CHANNELS].

In Rust I just had to unlearn using arrays. Not only they’re broken in Rust-specific ways, but often wrong approach given that Rust has tuples and generics.


#9

I have to unlearn cyclic data structures :slight_smile:

I struggled a lot with problems like “how do I represent a parent and a child” and “how do I store the owned value and a reference to it inside the single data structure”. After I’ve internalized what is impossible in Rust, it became natural to design data in way that completely sidesteps these problems.

This is a common problem I think: oftentimes somebody tries to bend Rust ownership rules with Rc, Weak and RefCell instead of trying to design around the issue.


#10

When writing python, I find myself trying to handle every possible failure or edge case. I’d love something like match. I feel dirty and unsafe when I’m able to just ignore some possible source of errors


#11

Do you have any good examples of how to rebalance a tree on node deletion? Because we don’t want pointers to the parents for the reasons you gave, I try to return a tuple of (deletedNode, spilled_nodes_who_need_a_new_home) and let the unwinding of the tree use the returned value.

But this is based on a recursive algorithm. For deep trees, the stack may become exhausted so we want an iterative solution.


#12

I happen to have two example of re balancing after insertion :slight_smile:

If you implement your tree recursively, without storing parent pointers, using either implicit or explicit stack, returning a tuple should work. I do this in my B-tree implementation: https://github.com/matklad/tree_bench/blob/2352434011287831ed0853bbe4b430d206357843/src/btree/node.rs#L57. Also, if your tree is balanced, you should not worry about exhausting the stack, because the height is logarithmic, so you will exhaust the heap first.

If you want a true non-recursive implementation with parent pointers, the best option imo is to go with unsafe *mut Node. This is precisely the case where you want to provide a safe abstraction (Tree) over the unsafe data structure implementation (Node). I do this in my Red-Black tree implementation: https://github.com/matklad/tree_bench/blob/2352434011287831ed0853bbe4b430d206357843/src/rbtree/node.rs#L79. IIRC, the std::collections::BTreeMap also uses similar approach with unsafe parent pointers.