Expand description
§Overview
An util crate to complete futures through a handle. Its main purpose is to bridge async Rust and callback-based APIs.
Inspired on the future_handles
crate.
The susync
crate uses standard library channels under the hood. It uses thread-safe primitives but expects low contention,
so it uses a single SpinMutex
for shared state. It should also work on no_std
environments but it is not tested.
By design handles are allowed to race to complete the future so it is ok to call complete on handle of a completed future. More info here.
§Examples
Channel-like API:
async fn func() -> Option<u32> {
let (future, handle) = susync::create();
func_with_callback(|res| {
handle.complete(res);
});
future.await.ok()
}
Scoped API:
async fn func() -> Option<u32> {
let future = susync::suspend(|handle| {
func_with_callback(|res| {
handle.complete(res);
});
});
future.await.ok()
}
§Thread safety
Currently it uses thread safe primitives so keep in mind that overhead.
§Danger!
Do NOT do this, it will block forever!
async fn func() {
let (future, handle) = susync::create();
// Start awaiting here...
future.await.unwrap();
// Now we'll never be able set the result!
handle.complete(1);
}
Awaiting a SuspendFuture
before setting the result with
SuspendHandle
will cause a deadlock!
§Macro shortcuts
If your use case is to simply to call complete
on the SuspendHandle
with the arguments of a callback, the sus
macro is an option.
// With one closure argument
fn func_one_arg(func: impl FnOnce(u32)) {
func(42);
}
// Here the arguments of the closure will be passed to `complete`
let result = sus!(func_one_arg(|x| {})).await.unwrap();
assert_eq!(result, 42);
// With two closure arguments
fn func_two_args(func: impl FnOnce(u32, f32)) {
func(42, 69.0);
}
// Here the arguments of the closure will be passed to `complete`
let result = sus!(func_two_args(|x, y| {})).await.unwrap();
assert_eq!(result, (42, 69.0));
The SuspendFuture
will hold the arguments in a tuple or just a type in case it’s just one argument.
You can ignore arguments by using the wildcard _
token.
fn func_with_callback(func: impl FnOnce(u32, &str)) {
func(42, "ignored :(");
}
// Here the second argument gets ignored
let result = sus!(func_with_callback(|x, _| {})).await.unwrap();
assert_eq!(result, 42);
§Macro invariants
The callback argument must be a closure.
This macro implementation revolves around generating a new closure that runs the original and also forwards the arguments to SuspendHandle::complete
.
The logic is wrapped in a suspend
that returns a future.
For this reason it’s not possible to accept anything else other than a closure because it’s not possible to infer the future output type.
The sus
macro calls to_owned
on all arguments so all arguments in the callback must implement ToOwned
trait.
This is to allow reference arguments like &str
and any other reference of a type that implements Clone
(check ToOwned
implementors).
Still looking for ways to overcome this limitation and give the user the freedom to choose how to complete the future.
// Does *NOT* implement `ToOwned`
struct RefArg(i32);
fn func(f: impl FnOnce(&RefArg)) {
f(&RefArg(42));
}
// *ILLEGAL*: Here the argument will clone a reference and try to outlive the scope
let RefArg(result) = sus!(func(|arg| {})).await.unwrap();
Unfortunately the error message is not very friendly because the error is trait bounds but they are implicit to the macro. So if you ever get an error message like below it is likely a reference is being passed to complete instead of an owned value.
error[E0521]: borrowed data escapes outside of closure
--> susync/src/lib.rs:127:22
|
13 | let RefArg(result) = sus!(func(|arg| {})).await.unwrap();
| ^^^^^^^^^^^---^^^^^^
| | |
| | `arg` is a reference that is only valid in the closure body
| `handle` declared here, outside of the closure body
| `arg` escapes the closure body here
|
= note: this error originates in the macro `sus`
In case there are more than one callback argument the macro only generates the boilerplate for the last one in the argument list. For no particular reason, just vague assumption that result callbacks come last.
fn func_with_callback(func1: impl FnOnce(i32), func2: impl FnOnce(f32)) {
func1(42);
func2(69.0);
}
// Here the macro only applies to the last closure
let result = sus!(func_with_callback(|_i| {}, |f| {})).await.unwrap();
assert_eq!(result, 69.0);
Macros§
- sus
- Generate the boilerplate for the use case where the future output is equal, or similar, to the callback arguments.
Structs§
- Suspend
Future - Future to suspend execution until
SuspendHandle
completes. - Suspend
Handle - Handle to signal a
SuspendFuture
that a result is ready.
Enums§
- Suspend
Error - The error returned by
SuspendFuture
in the case of the error variant.
Functions§
- create
- Creates a channel-like pair of
SuspendFuture
andSuspendHandle
. - suspend
- Creates a
SuspendFuture
from a closure.
Type Aliases§
- Suspend
Result - An ergonomic
Result
type to wrap standardResult
with errorSuspendError
.