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.