pub struct CacheableRepr<T: Debug + 'static, I: Fn(&T) -> bool> { /* private fields */ }
Expand description
Wraps a value and ensures that an invariant is maintained while allowing that value to be mutated. The invariant is checked after every mutation. Additionally, this struct allows for cacheable reads of the value. This is useful when the read function is expensive. By default, the caching is lazy, so after a value is read once that same read function will fetch the cached value unless the value has been mutated.
With the feature eager
enabled, the crate::EagerCacheLookup
trait is implemented for this struct
and can be used to cache values eagerly. Whenever the value is mutated, all eager caches
will be updated in parallel.
This struct requires that the value has a 'static
lifetime. If you need to store a value
with a non-static lifetime consider using Repr
.
Implementations§
Source§impl<T: Debug + 'static, I: Fn(&T) -> bool> CacheableRepr<T, I>
impl<T: Debug + 'static, I: Fn(&T) -> bool> CacheableRepr<T, I>
Sourcepub const fn new(inner: T, invariant: I) -> Self
pub const fn new(inner: T, invariant: I) -> Self
Creates a new representation invariant with the given value and invariant function.
use repr_rs::CacheableRepr;
#[derive(Debug)]
struct MinMax { min: i32, max: i32 }
CacheableRepr::new(
MinMax { min: 1, max: 5 },
|mm| mm.min < mm.max,
);
Sourcepub const fn with_msg(
inner: T,
invariant: I,
violation_message: &'static str,
) -> Self
pub const fn with_msg( inner: T, invariant: I, violation_message: &'static str, ) -> Self
Creates a new representation invariant with the given value, invariant function, and violation message.
use repr_rs::CacheableRepr;
#[derive(Debug)]
struct MinMax { min: i32, max: i32 }
CacheableRepr::with_msg(
MinMax { min: 1, max: 5 },
|mm| mm.min < mm.max,
"min must be less than max",
);
Sourcepub fn read(&self) -> &T
pub fn read(&self) -> &T
Borrows a read-only view of the value in the representation invariant.
use repr_rs::CacheableRepr;
#[derive(Debug)]
struct MinMax { min: i32, max: i32 }
let repr = CacheableRepr::new(MinMax { min: 1, max: 5 }, |mm| mm.min < mm.max);
let view = repr.read();
assert_eq!(1, view.min);
assert_eq!(5, view.max);
Sourcepub fn write(&mut self) -> ReprMutator<'_, T, I>
pub fn write(&mut self) -> ReprMutator<'_, T, I>
Borrows a mutable view of the value in the representation invariant.
use repr_rs::CacheableRepr;
#[derive(Debug)]
struct MinMax { min: i32, max: i32 }
let mut repr = CacheableRepr::new(MinMax { min: 1, max: 5 }, |mm| mm.min < mm.max);
{
let view = repr.read();
assert_eq!(1, view.min);
assert_eq!(5, view.max);
}
repr.write().min = 4;
let view = repr.read();
assert_eq!(4, view.min);
assert_eq!(5, view.max);
Rust’s borrowing rules prevent the read-only view being held while a mutation occurs. For example, this won’t compile:
use repr_rs::CacheableRepr;
#[derive(Debug)]
struct MinMax { min: i32, max: i32 }
let mut repr = CacheableRepr::new(MinMax { min: 1, max: 5 }, |mm| mm.min < mm.max);
let view = repr.read();
assert_eq!(1, view.min);
assert_eq!(5, view.max);
// error[E0502]: cannot borrow `repr` as mutable because it is also borrowed as immutable
repr.write().min = 4;
assert_eq!(4, view.min);
assert_eq!(5, view.max);
Sourcepub fn into_inner(self) -> T
pub fn into_inner(self) -> T
Consumes the representation invariant and returns the inner value.
use repr_rs::Repr;
#[derive(Debug)]
struct MinMax { min: i32, max: i32 }
let repr = Repr::new(MinMax { min: 1, max: 5 }, |mm| mm.min < mm.max);
let inner = repr.into_inner();
assert_eq!(1, inner.min);
Sourcepub fn lazy<R: Clone + 'static>(&mut self, read_fn: fn(&T) -> R) -> R
pub fn lazy<R: Clone + 'static>(&mut self, read_fn: fn(&T) -> R) -> R
Borrows a read-only view of the value in the representation invariant and caches the result of the read function. The cache is keyed by the read function’s address, so in general you should use function references instead of closures. It is a bug to perform any side effects in the read function (i.e. reading from a file).
use std::sync::atomic::{AtomicU32, Ordering};
use repr_rs::CacheableRepr;
#[derive(Debug)]
struct Person { name: String }
let mut repr = CacheableRepr::new(Person { name: "Alice and Bob together at last".into() }, |p| !p.name.is_empty());
static READ_SPY: AtomicU32 = AtomicU32::new(0);
fn expensive_read(p: &Person) -> usize {
// Just for demonstration purposes.
// Do not do side effects in your read functions!
READ_SPY.fetch_add(1, Ordering::Relaxed);
fib(p.name.len())
}
let fib_of_name_len = repr.lazy(expensive_read);
assert_eq!(832040, fib_of_name_len);
// this does not recompute the fibonacci number, it just gets it from the cache!
let fib_of_name_len2 = repr.lazy(expensive_read);
assert_eq!(832040, fib_of_name_len2);
repr.write().name = "Alice".into();
// this recomputes the fibonacci number because the name has changed
let fib_of_name_len3 = repr.lazy(expensive_read);
assert_eq!(5, fib_of_name_len3);
assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
Trait Implementations§
Source§impl<T: Debug + Clone + Sync + Send + 'static, I: Fn(&T) -> bool> EagerCacheLookup<T, I> for CacheableRepr<T, I>
impl<T: Debug + Clone + Sync + Send + 'static, I: Fn(&T) -> bool> EagerCacheLookup<T, I> for CacheableRepr<T, I>
Source§async fn eager<R: Clone + Sync + Send + 'static>(
&mut self,
read_fn: fn(&T) -> R,
) -> R
async fn eager<R: Clone + Sync + Send + 'static>( &mut self, read_fn: fn(&T) -> R, ) -> R
Borrows a read-only view of the value in the representation invariant and caches the
result of the read function. The cache is keyed by the read function’s address, so in general
you should use function references instead of closures. It is a bug to perform any side effects
in the read function (i.e. reading from a file). This cache is updated eagerly, so whenever
the value is mutated, all eager caches will be updated in parallel. See CacheableRepr::lazy
for
a lazy version of this function.
use std::time::Duration;
use std::sync::atomic::{AtomicU32, Ordering};
use repr_rs::{CacheableRepr, EagerCacheLookup};
#[derive(Debug, Clone)]
struct Person { name: String }
let mut repr = CacheableRepr::new(Person { name: "Alice and Bob together at last".into() }, |p| !p.name.is_empty());
static READ_SPY: AtomicU32 = AtomicU32::new(0);
fn expensive_read(p: &Person) -> usize {
// Just for demonstration purposes.
// Do not do side effects in your read functions!
READ_SPY.fetch_add(1, Ordering::Relaxed);
fib(p.name.len())
}
let fib_of_name_len = repr.eager(expensive_read).await;
assert_eq!(832040, fib_of_name_len);
// this does not recompute the fibonacci number, it just gets it from the cache!
let fib_of_name_len2 = repr.eager(expensive_read).await;
assert_eq!(832040, fib_of_name_len2);
assert_eq!(1, READ_SPY.load(Ordering::Relaxed));
repr.write().name = "Alice".into();
// if we wait a bit we can see that a new value has been computed
tokio::time::sleep(Duration::from_millis(100)).await;
assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
// Now when we fetch it again, we should see the new value without needing to recompute it
let fib_of_name_len3 = repr.eager(expensive_read).await;
assert_eq!(5, fib_of_name_len3);
assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
Source§impl<T: Debug + 'static, I: Fn(&T) -> bool> From<CacheableRepr<T, I>> for Repr<T, I>
impl<T: Debug + 'static, I: Fn(&T) -> bool> From<CacheableRepr<T, I>> for Repr<T, I>
Source§fn from(value: CacheableRepr<T, I>) -> Self
fn from(value: CacheableRepr<T, I>) -> Self
impl<T: Debug + Eq, I: Fn(&T) -> bool> Eq for CacheableRepr<T, I>
impl<T: Debug + Send, I: Fn(&T) -> bool + Send> Send for CacheableRepr<T, I>
§Safety
We exclusively own all inner values here (both the repr and the caches), so we can safely implement Send for this type.
impl<T: Debug + Sync, I: Fn(&T) -> bool + Sync> Sync for CacheableRepr<T, I>
The inner mutation (i.e. adding new caches or updating caches) either requires a mutable borrow or is guarded behind a lock.
Auto Trait Implementations§
impl<T, I> !Freeze for CacheableRepr<T, I>
impl<T, I> !RefUnwindSafe for CacheableRepr<T, I>
impl<T, I> Unpin for CacheableRepr<T, I>
impl<T, I> !UnwindSafe for CacheableRepr<T, I>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait>
(where Trait: Downcast
) to Box<dyn Any>
. Box<dyn Any>
can
then be further downcast
into Box<ConcreteType>
where ConcreteType
implements Trait
.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait>
(where Trait: Downcast
) to Rc<Any>
. Rc<Any>
can then be
further downcast
into Rc<ConcreteType>
where ConcreteType
implements Trait
.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait
(where Trait: Downcast
) to &Any
. This is needed since Rust cannot
generate &Any
’s vtable from &Trait
’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait
(where Trait: Downcast
) to &Any
. This is needed since Rust cannot
generate &mut Any
’s vtable from &mut Trait
’s.