stjepang
A month ago I wrote a blog post about the evolution of async Rust and its async runtimes. Hopefully this answers your questions!
https://stjepang.github.io/2020/04/03/why-im-building-a-new-...
A Whirlwind Tour of Distributed Systems
Of course, arenas do have tradeoffs - I'll try to summarize.
Pros:
- Completely safe.
- More ergonomic than dealing with `Rc<RefCell<T>>`.
- Improved cache efficiency of linked data structures.
- Fast node allocation and deallocation.
Cons:
- Any two simultaneous node accesses must be immutable.
- Accessing a node incurs the cost of a bound and liveness check.
- Vector growth can be costly and cause a spike in latency.
- Vector growth moves all nodes in memory.
- Removing a node creates a hole in the vector, possibly wasting memory.
Generally speaking, I think anyone learning Rust and trying to implement a graph data structure should first do it on top of a vector. It's easier than other commonly suggested approaches and it's not a bad one either. In fact, that's how rustc builds some of its data structures, how conrod (a GUI toolkit) represents its widget graph, and how Way Cooler (a window manager) manages window tilings.
Doubly-linked list: https://github.com/stjepang/vec-arena/blob/master/examples/l...
Splay tree: https://github.com/stjepang/vec-arena/blob/master/examples/s...
https://github.com/stjepang/vec-arena/blob/master/examples/s...
Another such case is when sorting strings because every comparison causes a potential cache miss and introduces even more branching, and all that would dwarf that one misprediction.
If comparison is cheap (e.g. when sorting integers), pdqsort wins because it copies less data around and the instructions are less data-dependency-heavy.
If comparison is expensive (e.g. when sorting strings), timsort is usually a tiny bit faster (around 5% or less) because it performs a slightly smaller total number of comparisons.
Fortunately, the problem is easy to fix and there's a brief mention of it in the comments: https://is.gd/txKd4I
It's up to the synchronization primitives to ensure memory barriers are executed, not the language. So e.g. a Mutex<T> would execute a memory barrier when locking and unlocking. Rust then allows you to pass ownership in a trusted manner only, i.e. through those primitives.
Of course, you can also create an unsafe block and say: "Rust, trust me, I know what I'm doing, don't bother me, and these are the invariants I want you to enforce in safe code..." Mutexes are implemented this way.
If your function returns Result<T, Box<Error>>, then you can use try! or ? operator to return any kind of error. It will be automatically boxed and converted into the generic Box<Error>.
This works for two reasons:
1) try! and ? don't simply return Err(err) on error case as one might think. They actually return Err(From::from(err)).
2) The standard library has: impl<'a, E: Error + 'a> From<E> for Box<Error + 'a> { ... }
Check out this article, starting from "The From trait": http://blog.burntsushi.net/rust-error-handling/