| Crates.io | completion_stage |
| lib.rs | completion_stage |
| version | 0.1.0 |
| created_at | 2025-06-22 15:11:40.479058+00 |
| updated_at | 2025-06-22 15:11:40.479058+00 |
| description | Push-based futures for Rust similar to Java's CompletionStage |
| homepage | |
| repository | https://github.com/AlexanderSchuetz97/completion_stage |
| max_upload_size | |
| id | 1721693 |
| size | 110,892 |
Push-based futures for Rust similar to Java's CompletionStage.
use std::thread;
use std::time::Duration;
use completion_stage::CompletionStage;
fn main() {
let result : String = CompletionStage::new_async::<thread::Thread>(|| {
// Executed in a new virgin thread via thread::spawn,
// you can provide your own executor via the generic instead of 'thread::Thread'
// by implementing a simple trait for your executor.
//
// Do some background task here
thread::sleep(Duration::from_secs(5));
//
// eventually return the result.
return 12345;
}).and_then_apply(|intermediate| {
// Executed in the same thread as above,
// or the main thread if the thread above is already finished,
// which is unlikely for this example
return format!("The result is {intermediate}");
}).unwrap();
println!("{}", result);
}
In the rust language 'futures' are poll-based and require use of 'async/await' which hides the callback-based execution flow from the programmer as well as requiring complex execution engines and frameworks that are unsuitable for a lot of situations.
This crate aims to provide futures that require no async execution engine, nor do they attempt to hide callback-based control flow from you.
This example here illustrates what is meant by push-based, functionally it's completely identical to the example above.
use std::thread;
use std::time::Duration;
use completion_stage::CompletionStage;
fn main() {
let first_stage : CompletionStage<i32> = CompletionStage::new();
{
//The CompletionStage is reference counted so it can be sent/shared with any thread.
let first_stage = first_stage.clone();
thread::spawn(move || {
thread::sleep(Duration::from_secs(5));
//'Push' the value into the future and execute all further child stages right here!
first_stage.complete_with_value(12345i32)
});
}
let second_stage : CompletionStage<String> = first_stage.and_then_apply(|intermediate : i32| {
// Executed in the same thread as above,
// or the main thread if the thread above is already finished,
// which is unlikely for this example
return format!("The result is {intermediate}");
});
println!("{}", second_stage.unwrap());
}
This crate does not catch any panic using panic::catch_unwind. If any user code panic's then a "drop guard" ensures that all dependent stages will be completed and in turn have their user code invoked during unwinding with Completion::Panic. This means that if user code panics again during this unwinding, then the application will abort as it normally does on a panic during unwind.
The panic value itself is not caught and therefore not available to dependent stages, as rust panic's should not be used in lieu of exceptions.
If you compile your code with panic=abort then none of this matters to you.
The implementation automatically detects simple deadlocks. All deadlocks that are caused by a thread waiting on itself are detected. For example, borrowing the value of a stage while trying to take the value from the stage is detected. This generally causes the taking operation to fail. Some taking operation's panic in this case, which is made explicit in their documentation.
I intend to use this crate to "return" results to an opengl ui-thread. The opengl ui "thread" will start background tasks when, for example, a "button" is pressed but should not be blocked (as that would freeze the UI).
Currently, I have been using a lot of janky code that uses mpsc Channels and try_recv for this purpose, but I intend to replace all such code with this crate.