context-rs

Go has a history of suggesting you provide a ctx context.Context parameter to all functions in an async context, such as web servers. This is useful for passing deadlines and such down into the callstack, to allow leaf-functions to schedule shutdowns.

Rust already passes a context value automatically for you in all async functions, this is already named Context but it's heavily under-featured - only providing a 'wake-up' handle.

Making use of the nightly Provider API, we can modify this context to provide values on demand down the callstack. This avoids using thread_locals, which requires std, or passing through a TypeMap with every function call, which is unergonomic and requires alloc.

Examples

A demonstration of an async deadline, using get_value and provide_ref

```rust use contextrs::{getvalue, ProviderFutExt}; use std::time::{Instant, Duration};

// New type makes it easier to have unique keys in the context

[derive(Clone)]

struct Deadline(Instant);

[derive(Debug, PartialEq)]

struct Expired;

impl Deadline { // check if the deadline stored in the context has expired // returns OK if no deadline is stored. async fn expired() -> Result<(), Expired> { getvalue().await.map(|Deadline(deadline)| { // if there is a deadline set, check if it has expired if deadline < Instant::now() { Err(Expired) } else { Ok(()) } }).unwrapor(Ok(())) // or ignore it if no deadline is set } }

// some top level work - agnostic to the context async fn somework() -> Result<(), Expired> { loop { somenested_function().await? } }

// some deeply nested work, cares about the deadline context async fn somenestedfunction() -> Result<(), Expired> { // will acquire the deadline from the context itself Deadline::expired().await?;

// do some logic in here

Ok(())

}

[tokio::main]

async fn main() { // timeout in 2 seconds let deadline = Instant::now() + Duration::from_secs(2);

let res = some_work().provide_ref(&Deadline(deadline)).await;

assert_eq!(res, Err(Expired));

} ```

If you only need to access the value temporarily, and the value you want is expensive to clone, you can use with_ref instead of get_value. This will accept a closure with the ref provided for a short lived lifetime.