BorrowMutex
Very initial version! Use with caution
[BorrowMutex] is an async Mutex which does not require wrapping the target
structure. Instead, a &mut T can be lended to the mutex at any given time.
This lets any other side borrow the &mut T. The mutable ref is borrow-able
only while the lender awaits, and the lending side can await until someone
wants to borrow. The semantics enforce at most one side has a mutable reference
at any given time.
This lets us share any mutable object between distinct async contexts
without Arc<Mutex> over the object in question and without relying
on any kind of internal mutability. It's mostly aimed at single-threaded
executors where internal mutability is an unnecessary complication.
Still, the [BorrowMutex] is Send+Sync and can be safely used from
any number of threads.
The most common use case is having a state handled entirely in its own async context, but occasionally having to be accessed from the outside - another async context.
Since the shared data doesn't have to be wrapped inside an Arc,
it doesn't have to be allocated on the heap. In fact, BorrowMutex does not
perform any allocations whatsoever. The
tests/borrow_basic.rs
presents a simple example where everything is stored on the stack.
Safety
The API is unsound when futures are forgotten ([core::mem::forget()]).
For convenience, none of the API is marked unsafe.
See [BorrowMutex::lend] for details.
Hopefully the unsound code could be prohibited in future rust versions with additional compiler annotations.
Example
use BorrowMutex;
use FutureExt;
let mutex = new;
let f1 = async ;
let f2 = async ;
block_on;
Both futures should print interchangeably. See tests/borrow_basic.rs for
a full working example.
What if Drop is not called?
Unfortunately, Undefined Behavior. With [core::mem::forget()] or similar
called on [LendGuard] we can make the borrow checker believe the lended
&mut T is no longer used, while in fact, it is:
# use BorrowMutex;
# use Future;
# use Context;
# use Poll;
# use pin;
let mutex = new;
let mut test = TestStruct ;
let mut test_borrow = pin!;
let _ = test_borrow
.as_mut
.poll;
let mut t1 = Boxpin;
let _ = t1
.as_mut
.poll;
forget;
// the compiler thinks `test` is no longer borrowed, but in fact it is
let Ready = test_borrow
.as_mut
.poll
else ;
// now we get two mutable references, this is strictly UB
test_borrow.counter = 2;
test.counter = 6;
assert_eq!; // this fails
Similar Rust libraries make their API unsafe exactly because of this reason -
it's the caller's responsibility to not call [core::mem::forget()] or similar
(async-scoped)
However, this Undefined Behavior is really difficult to trigger in regular
code. It's hardly useful to call [core::mem::forget()] on a future.