Expand description

Abstracting over accessing parts of stored value.

Sometimes, there’s a big globalish data structure (like a configuration for the whole program). Then there are parts of the program that need access to up-to-date version of their part of the configuration, but for reasons of code separation and reusability, it is not desirable to pass the whole configuration to each of the parts.

This module provides means to grant the parts access to the relevant subsets of such global data structure while masking the fact it is part of the bigger whole from the component.

Note that the cache module has its own Access trait that serves a similar purpose, but with cached access. The signatures are different, therefore an incompatible trait.

The general idea

Each part of the code accepts generic Access<T> for the T of its interest. This provides means to load current version of the structure behind the scenes and get only the relevant part, without knowing what the big structure is.

For technical reasons, the Access trait is not object safe. If type erasure is desired, it is possible use the DynAccess instead, which is object safe, but slightly slower.

For some cases, it is possible to use ArcSwapAny::map. If that is not flexible enough, the Map type can be created directly.

Note that the Access trait is also implemented for ArcSwapAny itself. Additionally, there’s the Constant helper type, which is useful mostly for testing (it doesn’t allow reloading).

Performance

In general, these utilities use ArcSwapAny::load internally and then apply the provided transformation. This has several consequences:

  • Limitations of the load apply ‒ including the recommendation to not hold the returned guard object for too long, but long enough to get consistency.
  • The transformation should be cheap ‒ optimally just borrowing into the structure.

Examples

use std::sync::Arc;
use std::thread::{self, JoinHandle};
use std::time::Duration;

use arc_swap::ArcSwap;
use arc_swap::access::{Access, Constant, Map};

fn work_with_usize<A: Access<usize> + Send + 'static>(a: A) -> JoinHandle<()> {
    thread::spawn(move || {
        let mut value = 0;
        while value != 42 {
            let guard = a.load();
            value = *guard;
            println!("{}", value);
            // Not strictly necessary, but dropping the guard can free some resources, like
            // slots for tracking what values are still in use. We do it before the sleeping,
            // not at the end of the scope.
            drop(guard);
            thread::sleep(Duration::from_millis(50));
        }
    })
}

// Passing the whole thing directly
// (If we kept another Arc to it, we could change the value behind the scenes)
work_with_usize(Arc::new(ArcSwap::from_pointee(42))).join().unwrap();

// Passing a subset of a structure
struct Cfg {
    value: usize,
}

let cfg = Arc::new(ArcSwap::from_pointee(Cfg { value: 0 }));
let thread = work_with_usize(Map::new(Arc::clone(&cfg), |cfg: &Cfg| &cfg.value));
cfg.store(Arc::new(Cfg { value: 42 }));
thread.join().unwrap();

// Passing a constant that can't change. Useful mostly for testing purposes.
work_with_usize(Constant(42)).join().unwrap();

Structs

Access to an constant.

An adaptor to provide access to a part of larger structure.

Traits

Abstracts over ways code can get access to a value of type T.

An object-safe version of the Access trait.