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
SuspendHandlecompletes. - Suspend
Handle - Handle to signal a
SuspendFuturethat a result is ready.
Enums§
- Suspend
Error - The error returned by
SuspendFuturein the case of the error variant.
Functions§
- create
- Creates a channel-like pair of
SuspendFutureandSuspendHandle. - suspend
- Creates a
SuspendFuturefrom a closure.
Type Aliases§
- Suspend
Result - An ergonomic
Resulttype to wrap standardResultwith errorSuspendError.