Withoutboats, a Rust language contributor, writes about Rust language design, async, pinning and governance.
Commenters recommend it as a source for Rust fundamentals and language design, especially the posts on a smaller Rust and async deadlocks.
> Tracing GC is the simplest model for the user, and helps with time management and development velocity, two very important aspects of software engineering.
> Borrow checking is very fast, and helps avoid data races.
One thing many people seem to assume is that not having to care about memory means you can program faster and get to your goal faster. As the author here seems to do. However as it turns out, if your program is more complex than a ~100-1000 lines of codes, explaining in a explicit way who owns what and who gets to change state when is a very useful way to avoid bugs.
Saoirse Shipwreckt aka withoutboats mentioned this a while ago in https://without.boats/hire-me/
> Rust works because it enables users to write in an imperative programming style, which is the mainstream style of programming that most users are familiar with, while avoiding to an impressive degree the kinds of bugs that imperative programming is notorious for. As I said once, pure functional programming is an ingenious trick to show you can code without mutation, but Rust is an even cleverer trick to show you can just have mutation.
and later follows up on this in https://without.boats/blog/revisiting-a-smaller-rust/
> I still think this is Rust’s “secret sauce” and it does mean what I said: the language would have to have ownership and borrowing. But what I’ve realized since is that there’s a very important distinction between the cases in which users want these semantics and the cases where…
This post is completely and totally wrong. At least you got to ruin my day, I hope that's a consolation prize for you.
There is NO meaningful connection between the completion vs polling futures model and the epoll vs io-uring IO models. comex's comments regarding this fact are mostly accurate. The polling model that Rust chose is the only approach that has been able to achieve single allocation state machines in Rust. It was 100% the right choice.
After designing async/await, I went on to investigate io-uring and how it would be integrated into Rust's system. I have a whole blog series about it on my website: https://without.boats/tags/io-uring/. I assure you, the problems it present are not related to Rust's polling model AT ALL. They arise from the limits of Rust's borrow system to describe dynamic loans across the syscall boundary (i.e. that it cannot describe this). A completion model would not have made it possible to pass a lifetime-bound reference into the kernel and guarantee no aliasing. But all of them have fine solutions building on work that already exists.
Pin is not a hack any more than Box is. It is the only way to fit the desired ownership expression into the language that already exists, squaring these requirements with other desireable primitives we had already committed to shared ownership pointers, mem::swap, etc. It is simply FUD - frankly, a lie - to say that it will block "noalias," following that link shows Niko and Ralf having a fruitful…
withoutboats has explored these trade-offs on their blog, e.g. https://without.boats/blog/poll-drop/ and as part of io-uring investigation. io-uring is the main use-case for async drop, but even that has been mostly solved without language changes: there is tokio-uring now.
Apart from requiring a safe zero-copy io-uring abstraction to use a buffer pool instead of "bare" references, Rust's polling+cancellation model is fantastic. It's incredibly convenient to be able to externally time out and abort any Future, without needing to pass contexts/tokens and/or needing every I/O operation to have an explicit timeout option.
It's annoying when conscious small trade-offs are framed as fatal flaws (like the title of TFA). In the case of the cancellation model the "doesn't work" is that a safe abstraction is not entirely zero-cost. But when you use async in JS, C#, or Golang, you already have unavoidable heap allocations and memcpys (and another set of difficulties with cancellation). Rust's worst-case flaw is comparable to a normal mode of operation in a few other async languages. The scale of the issue is overblown, and its benefits are overlooked.
> Why isn’t there a variant of &mut that doesn’t allow swapping the value?
This is a very insightful observation, and Niko Matsakis (leading influence of Rust's borrow checker) would likely agree with you that this is an instance where Rust's default borrowing rules are probably too permissive, in the sense that being more restrictive by default regarding the "swappability" of &mut could lead to Rust being able to provide more interesting static guarantees. See his blog post here: https://smallcultfollowing.com/babysteps/blog/2024/09/26/overwrite-trait/
> Why is pinning a weird sticky property of a reference? Shouldn’t non-movability of an object be a property of the object’s type?
See this blog post from withoutboats: https://without.boats/blog/pinned-places/ for arguments as to why pinning is properly modeled as a property of a place rather than a type (particularly the section "Comparison to immovable types"), as well as this post from Niko that ties this point in with the above point regarding swappability: https://smallcultfollowing.com/babysteps/blog/2024/10/14/overwrite-and-pin/
https://github.com/tailscale/tailscale/pull/2370 was a practical drive toward this, will not proceed on this path.
much more approachable, boats has written about challenges integrating in rust: https://without.boats/tags/io-uring/
in the most general form: you need a fairly "loose" memory model to integrate the "best" (performance wise) parts, and the "best" (ease of use/forward looking safety) way to integrate requires C library linkage. This is troublesome in most GC languages, and many managed runtimes. There's also the issue that uring being non-portable means that the things it suggests you must do (such as say pinning a buffer pool and making APIs like read not immediate caller allocates) requires a substantially separate API for this platform than for others, or at least substantial reworks over all the existing POSIX modeled APIs - thus back to what I said originally, we need a replacement for POSIX & BSD here, broadly applied.
While I think the problem highlighted in the article is a longstanding problem for Rust[1], I don't think the example, or finalizers was the problem with Futurelock as described by Oxide.
I'm not sure you can write a simple example in Python, because Rust's future's architecture and Python's is different. `futurelock` is an issue of cancellation safety which is a stranger concept (related to finalizers, but not in the way OP has described).
Personally, I think `tokio::select!` is dangerous and I don't use it my code - it's very easy to deadlock yourself or create weird performance issues. I think the interface is too close to Go and if you don't understand what is going on, you can create deadlocks. That said, even if you avoid `tokio::select!`, I think cancellation safety is one of those dragons that exist in async rust.
[1] https://without.boats/blog/poll-drop/