←back to thread

495 points guntars | 2 comments | | HN request time: 0.001s | source
Show context
Seattle3503 ◴[] No.44981374[source]
> For example when submitting a write operation, the memory location of those bytes must not be deallocated or overwritten.

> The io-uring crate doesn’t help much with this. The API doesn’t allow the borrow checker to protect you at compile time, and I don’t see it doing any runtime checks either.

I've seen comments like this before[1], and I get the impression that building a a safe async Rust library around io_uring is actually quite difficult. Which is sort of a bummer.

IIRC Alice from the tokio team also suggested there hasn't been much interest in pushing through these difficulties more recently, as the current performance is "good enough".

[1] https://boats.gitlab.io/blog/post/io-uring/

replies(7): >>44981390 #>>44981469 #>>44981966 #>>44982846 #>>44983850 #>>44983930 #>>44989979 #
jcranmer ◴[] No.44981469[source]
There is, I think, an ownership model that Rust's borrow checker very poorly supports, and for lack of a better name, I've called it hot potato ownership. The basic idea is that you have a buffer which you can give out as ownership in the expectation that the person you gave it to will (eventually) give it back to you. It's a sort of non-lexical borrowing problem, and I very quickly discovered when trying to implement it myself in purely safe Rust that the "giving the buffer back" is just really gnarly to write.
replies(3): >>44981493 #>>44981689 #>>44982450 #
pornel ◴[] No.44982450[source]
This can be done with exclusively owned objects. That's how io_uring abstractions work in Rust – you give your (heap allocated) buffer to a buffer pool, and get it back when the operation is done.

&mut references are exclusive and non-copyable, so the hot potato approach can even be used within their scope.

But the problem in Rust is that threads can unwind/exit at any time, invalidating buffers living on the stack, and io_uring may use the buffer for longer than the thread lives.

The borrow checker only checks what code is doing, but doesn't have power to alter runtime behavior (it's not a GC after all), so it only can prevent io_uring abstractions from getting any on-stack buffers, but has no power to prevent threads from unwinding to make on-stack buffer safe instead.

replies(2): >>44983643 #>>44987564 #
alfiedotwtf ◴[] No.44983643[source]
In my universe, `let` wouldn’t exist… instead there would only be 3 ways to declare variables:

  1. global my_global_var: GlobalType = …
  2. heap my_heap_var: HeapType = …
  3. stack my_stack_var: StackType = …
 
Global types would need to implement a global trait to ensure mutual exclusion (waves hands).

So by having the location of allocation in the type itself, we no longer have to do boxing mental gymnastics

replies(2): >>44984696 #>>44986248 #
1. kbolino ◴[] No.44986248[source]
But what does "heap my_heap_var" actually mean, without a garbage collector? Who owns "my_heap_var" and when does it get deallocated? What does explicitly writing out the heap-ness of a variable ultimately provide, that Rust's existing type system with its many heap-allocated types (Box, Rc, Arc, Vec, HashMap, etc.) doesn't already provide?
replies(1): >>44996133 #
2. alfiedotwtf ◴[] No.44996133[source]
> What does explicitly writing out the heap-ness of a variable ultimately provide, that Rust's existing type system with its many heap-allocated types (Box, Rc, Arc, Vec, HashMap, etc.) doesn't already provide?

To be honest, I was thinking more in terms of cognitive overload i.e. is all that Box boilerplate even needed if we were to treat all `heap my_heap = …” as box underneath? In other words, couldn’t we elide all that away:

    let foo = Box::new(MyFoo::default ());
Becomes:

    heap foo = MyFoo::default();
Must nicer!