Make Any Function Async with sync_async
Published on December 04, 2024
Working with Rust's async ecosystem can often feel like navigating a tightrope. The strict boundaries between synchronous and asynchronous code, while ensuring performance and safety, can present challenges when integrating legacy synchronous code with modern asynchronous workflows. The async_sync
crate aims to address these challenges by providing robust tools to simplify sync-to-async transitions with features like retries, timeouts, diagnostics, and more.
Before we dive in, it's worth noting the existence of the maybe_async
crate, which provides a powerful macro-based approach for writing code that can work in both sync and async contexts. While maybe_async
is a fantastic tool for abstracting over sync/async code, async_sync
takes a more focused approach, offering practical, implementation-oriented utilities to manage specific scenarios where synchronous Rust code needs to coexist and integrate with async ecosystems.
Whether you’re working with legacy code, performing CPU-intensive computations, or handling transient failures, async_sync
equips you with tools tailored to bridge these sync-async gaps seamlessly.
Why Choose async_sync?
async_sync
provides:
- Implementation-Focused Design: Tools like retries, backoff strategies, and timeout handling solve real-world challenges without relying on abstraction layers.
- Compatibility with Any Async Ecosystem: Works seamlessly with popular runtimes like
Tokio
andAsyncStd
. - Built-In Diagnostics: Capture execution times and errors for better debugging and performance insights.
- Developer-Friendly API: High-level utilities minimize boilerplate, making sync-to-async transitions straightforward.
Features in Action
Let’s explore the key features of async_sync
with real-world examples based on its comprehensive test suite.
1. Retry Mechanism with Backoff Strategies
Retries are essential for transient errors like network issues. async_sync
makes it easy to implement retries with configurable backoff strategies, such as constant or exponential delays.
#[tokio::test]
async fn test_sync_to_async_with_retries_success() {
let counter = Arc::new(Mutex::new(0));
let result = sync_to_async_with_retries(
|| flaky_function(Arc::clone(&counter)),
5,
Duration::from_millis(100),
Backoff::Constant,
).await;
assert_eq!(result.unwrap(), "Success");
}
/// Mock function for retries
fn flaky_function(counter: Arc<Mutex<i32>>) -> Result<String, &'static str> {
let mut count = counter.lock().unwrap();
*count += 1;
if *count < 3 {
Err("Failure")
} else {
Ok("Success".to_string())
}
}
2. Timeout Handling with Cleanup
Ensure that long-running tasks don’t block indefinitely by adding timeouts. With async_sync
, you can also specify a cleanup action to execute if the timeout occurs.
#[tokio::test]
async fn test_sync_to_async_with_timeout_and_cleanup() {
let result = sync_to_async_with_timeout_and_cleanup(
|| heavy_computation(5),
Duration::from_millis(50),
cleanup_action,
).await;
assert!(result.is_err());
}
/// Mock computation
fn heavy_computation(x: i32) -> i32 {
std::thread::sleep(Duration::from_millis(200));
x * x
}
/// Mock cleanup action
fn cleanup_action() {
println!("Cleanup executed!");
}
3. Parallel Execution of Sync Tasks
Run multiple synchronous tasks in parallel to maximize efficiency, especially for independent CPU-bound operations.
#[tokio::test]
async fn test_parallel_sync_to_async() {
let tasks: Vec<_> = (1..=5).map(|x| move || heavy_computation(x)).collect();
let results = parallel_sync_to_async(tasks).await;
for (i, result) in results.into_iter().enumerate() {
assert_eq!(result.unwrap(), (i + 1).pow(2));
}
}
4. Detailed Diagnostics
Log execution times of synchronous tasks to gain insight into performance.
#[tokio::test]
async fn test_sync_to_async_with_diagnostics() {
let result = sync_to_async_with_diagnostics(|| heavy_computation(5)).await;
assert_eq!(result.unwrap(), 25);
}
5. Flexible Argument Passing
Simplify passing arguments to synchronous functions using sync_to_async_with_args
.
#[tokio::test]
async fn test_sync_to_async_with_args() {
let result = sync_to_async_with_args(|x: i32| heavy_computation(x), 5).await;
assert_eq!(result.unwrap(), 25);
}
6. Sync-Async Integration with Multiple Runtimes
Integrate synchronous functions into async workflows, choosing between Tokio
or AsyncStd
runtimes.
#[test]
fn test_sync_to_async_with_runtime_tokio() {
let result = sync_to_async_with_runtime(Runtime::Tokio, || heavy_computation(4));
assert_eq!(result.unwrap(), 16);
}
7. Aggregation of Results
Combine results from multiple synchronous tasks in a batch, ensuring all tasks are executed and their results returned.
#[tokio::test]
async fn test_aggregate_results() {
let tasks: Vec<_> = (1..=3).map(|x| move || heavy_computation(x)).collect();
let results = aggregate_results(tasks).await;
for (i, result) in results.into_iter().enumerate() {
assert_eq!(result.unwrap(), (i + 1).pow(2));
}
}
The async_sync
crate is your go-to tool for managing sync-async transitions in Rust. Its feature set includes retries, timeouts, parallel execution, and diagnostics—all tested and ready for real-world use.
Whether you're integrating legacy code or building new async systems, async_sync
ensures seamless operation and robust performance, reducing the complexity of bridging synchronous and asynchronous Rust codebases.
By simplifying these interactions, async_sync
allows you to focus on your core logic rather than managing sync-async boundaries.
Getting Started
- Add
async_sync
to yourCargo.toml
:
[dependencies]
async_sync = "0.1.0"
- Import and use the library in your project:
use async_sync::{sync_to_async_with_retries, Backoff};
#[tokio::main]
async fn main() {
let result = sync_to_async_with_retries(
|| some_sync_function(),
3,
Duration::from_millis(200),
Backoff::Exponential,
).await;
match result {
Ok(value) => println!("Task succeeded with value: {:?}", value),
Err(err) => eprintln!("Task failed with error: {:?}", err),
}
}
This snippet demonstrates using the sync_to_async_with_retries
function with exponential backoff to handle transient failures in a synchronous function.
Start bridging the gap between sync and async in Rust today with async_sync
. It's lightweight, powerful, and designed to make your Rust development smoother and more efficient!
Check out this YouTube video. It's a great introduction to the async_sync
crate and how it can help you bridge the gap between sync and async code in Rust.
For more information, check out the official async_sync
documentation and GitHub repository.