Skip to main content

LocalKeyImmutable

Struct LocalKeyImmutable 

Source
pub struct LocalKeyImmutable<T: 'static>(/* private fields */);
Expand description

An immutable task-local storage key.

This type provides task-local storage that cannot be modified once set within a scope, offering stronger guarantees than LocalKey about value stability.

§Immutability Semantics

The term “immutable” here has specific semantics that may be counterintuitive:

  • Within a scope: Once set via scope(), the value cannot be changed by set() or replace() methods (these methods don’t exist)
  • Across scopes: The same task-local can have different values in nested scopes, and a future may observe different values as it executes across scope boundaries
  • Primary use case: Preventing downstream code from modifying configuration or context values that should remain stable

§Examples

task_local! {
    static const CONFIG: String;
    static MUTABLE_STATE: Vec<String>;
}

async fn demonstrate_immutability() {
    CONFIG.scope("production".to_string(), async {
        // This would not compile - no set() method:
        // CONFIG.set("development".to_string());
         
        // Access the immutable value
        CONFIG.with(|cfg| {
            assert_eq!(cfg.as_ref().unwrap().as_str(), "production");
        });
         
        // But MUTABLE_STATE can be modified:
        MUTABLE_STATE.scope(vec![], async {
            MUTABLE_STATE.with_mut(|v| {
                if let Some(vec) = v {
                    vec.push("log".to_string());
                }
            });
        }).await;
    }).await;
}

§When to Use

Use LocalKeyImmutable for:

  • Request IDs, trace IDs, and correlation identifiers
  • User context and authentication information
  • Environment configuration
  • Any value that should remain constant within a scope

Use regular LocalKey for:

  • Accumulators and counters
  • Mutable state that changes during execution
  • Caches and temporary storage

Implementations§

Source§

impl<T: 'static> LocalKeyImmutable<T>

Source

pub fn scope<F>( &'static self, value: T, f: F, ) -> impl Future<Output = F::Output>
where F: Future, T: Unpin,

Sets the immutable task-local value for the duration of a future.

Unlike LocalKey::scope, the value cannot be modified once set within the scope. This is useful for configuration values that should remain constant during execution.

§Examples
task_local! {
    static const ENVIRONMENT: String;
}

async fn log_with_env(message: &str) {
    ENVIRONMENT.with(|env| {
        println!("[{}] {}", env.unwrap(), message);
    });
}

async fn main_task() {
    ENVIRONMENT.scope("production".to_string(), async {
        log_with_env("Starting server").await;
         
        // Unlike mutable task-locals, this would not compile:
        // ENVIRONMENT.set("development".to_string()); // Error!
         
        ENVIRONMENT.scope("staging".to_string(), async {
            log_with_env("In staging context").await;
        }).await;
         
        log_with_env("Back in production").await;
    }).await;
}
Source

pub fn get(&'static self) -> T
where T: Copy,

Returns a copy of the immutable task-local value.

§Panics

Panics if the task-local value is not set.

§Examples
task_local! {
    static const USER_ID: u64;
}

async fn get_user_permissions() -> Vec<String> {
    let user_id = USER_ID.get();
    // Fetch permissions for user_id...
    vec![format!("read:user:{}", user_id)]
}

async fn example() {
    USER_ID.scope(12345, async {
        let perms = get_user_permissions().await;
        assert_eq!(perms[0], "read:user:12345");
    }).await;
}
Source

pub fn with<F, R>(&'static self, f: F) -> R
where F: FnOnce(Option<&T>) -> R,

Accesses the immutable task-local value through a closure.

This is the safest way to access an immutable task-local value as it handles the case where the value might not be set.

§Examples
task_local! {
    static const REQUEST_ID: String;
}

fn log_with_request_id(message: &str) {
    REQUEST_ID.with(|id| {
        match id {
            Some(request_id) => println!("[{}] {}", request_id, message),
            None => println!("[no-request-id] {}", message),
        }
    });
}

async fn handle_request() {
    log_with_request_id("Processing request");
}

async fn example() {
    // Without request ID
    handle_request().await;
     
    // With request ID
    REQUEST_ID.scope("req-123".to_string(), async {
        handle_request().await;
    }).await;
}

Trait Implementations§

Source§

impl<T: Debug + 'static> Debug for LocalKeyImmutable<T>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.