Context
A Rust implementation of Go-style context propagation with async support.
Overview
This crate provides a context propagation mechanism inspired by Go's context package. It allows you to carry deadlines, cancellation signals, and key-value pairs through your program's call stack.
Installation
Add this to your Cargo.toml:
[dependencies]
rs_ctx = "0.1.0"
Then in your code, you can use it either directly:
use rs_ctx::{ Context, self as context };
Features
- Context cancellation with explicit cancel function
- Deadline/timeout support
- Type-safe key-value storage with generic types
- Hierarchical context inheritance
- Async-first design with Tokio runtime support
Dependencies
tokio (with "full" features) for async runtime support
serde and bincode for key serialization
futures for async utilities
thiserror for error handling
Examples
Basic Context Operations
use rs_ctx::{background, with_value, with_cancel, with_deadline, with_timeout};
use std::time::{Duration, Instant};
#[tokio::main]
async fn main() {
let ctx = background();
assert!(ctx.err().is_none());
assert!(ctx.deadline().is_none());
let ctx = with_value(&ctx, "key", "value".to_string()).await;
let (ctx, cancel) = with_cancel(&ctx);
let deadline = Instant::now() + Duration::from_secs(5);
let (ctx, _) = with_deadline(&ctx, deadline);
let (ctx, _) = with_timeout(&ctx, Duration::from_secs(1));
if let Some(value) = ctx.value::<_, String>(&"key") {
println!("Value: {}", value);
}
cancel.cancel().await;
assert_eq!(ctx.err(), Some(ContextError::Canceled));
}
Type-Safe Value Storage
use serde::Serialize;
#[derive(Hash, Eq, PartialEq, Clone, Serialize)]
struct BoolKey(String);
#[derive(Clone)]
struct CustomValue(Vec<u32>);
async fn type_safe_example() {
let ctx = background();
let ctx = with_value(&ctx, "str_key", "test".to_string()).await;
let ctx = with_value(&ctx, 42_i32, 42_i64).await;
let ctx = with_value(&ctx, BoolKey("bool".into()), true).await;
let str_value: Option<Arc<String>> = ctx.value(&"str_key");
let int_value: Option<Arc<i64>> = ctx.value(&42_i32);
let bool_value: Option<Arc<bool>> = ctx.value(&BoolKey("bool".into()));
assert_eq!(str_value.map(|v| (*v).clone()), Some("test".to_string()));
assert_eq!(int_value.map(|v| (*v).clone()), Some(42_i64));
assert_eq!(bool_value.map(|v| (*v).clone()), Some(true));
}
Context Inheritance
async fn inheritance_example() {
let root = background();
let root = with_value(&root, "root_key", "root_value").await;
let child = with_value(&root, "child_key", "child_value").await;
let deadline = Instant::now() + Duration::from_secs(1);
let (grandchild, _) = with_deadline(&child, deadline);
let grandchild = with_value(&grandchild, "grandchild_key", "grandchild_value").await;
assert_eq!(grandchild.value::<_, String>(&"root_key").map(|v| (*v).clone()),
Some("root_value".into()));
assert_eq!(grandchild.value::<_, String>(&"child_key").map(|v| (*v).clone()),
Some("child_value".into()));
assert_eq!(grandchild.value::<_, String>(&"grandchild_key").map(|v| (*v).clone()),
Some("grandchild_value".into()));
assert_eq!(child.value::<_, String>(&"grandchild_key"), None);
assert_eq!(grandchild.deadline(), Some(deadline));
assert!(child.deadline().is_none());
}
Using with Tokio Tasks
use tokio::sync::mpsc;
async fn task_example() {
let ctx = background();
let (ctx, cancel) = with_cancel(&ctx);
let handle = tokio::spawn({
let ctx = ctx.clone();
async move {
tokio::select! {
_ = ctx.done() => {
"cancelled"
}
_ = tokio::time::sleep(Duration::from_secs(10)) => {
"timeout"
}
}
}
});
tokio::time::sleep(Duration::from_millis(100)).await;
cancel.cancel().await;
assert_eq!(handle.await.unwrap(), "cancelled");
}
Sharing Values Across Tasks
async fn shared_values_example() {
let ctx = background();
let ctx = with_value(&ctx, "shared_key", "shared_value").await;
let mut handles = vec![];
for i in 0..3 {
let ctx = ctx.clone();
handles.push(tokio::spawn(async move {
if let Some(value) = ctx.value::<_, String>(&"shared_key") {
format!("task_{}_got_{}", i, value.as_ref())
} else {
format!("task_{}_no_value", i)
}
}));
}
for (i, handle) in handles.into_iter().enumerate() {
let result = handle.await.unwrap();
assert_eq!(result, format!("task_{}_got_shared_value", i));
}
}
Task Cancellation and Cleanup
async fn cleanup_example() {
let ctx = background();
let (ctx, cancel) = with_cancel(&ctx);
let (tx, mut rx) = mpsc::channel(1);
let handle = tokio::spawn({
let ctx = ctx.clone();
let tx = tx.clone();
async move {
let result = tokio::select! {
_ = ctx.done() => {
tx.send("cleanup").await.unwrap();
"cancelled"
}
_ = tokio::time::sleep(Duration::from_secs(10)) => {
"timeout"
}
};
result
}
});
cancel.cancel().await;
assert_eq!(handle.await.unwrap(), "cancelled");
assert_eq!(rx.recv().await.unwrap(), "cleanup");
}
Part of the Jaxon Framework
This crate is one of the core components of the Jaxon framework. Each component is published as a separate crate for flexibility:
jaxon - The main framework crate
jaxon-context - Context propagation (this crate)
jaxon-runtime - Async runtime utilities
jaxon-macros - Procedural macros for the framework
- ... (other Jaxon components)
Each component can be used independently or together with other components. This modular design allows you to only include the functionality you need in your project.
License
This project is licensed under the Apache License, Version 2.0 - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.