The library uses a flat set of independent pointer traits to abstract over pointer strategies (Box vs Rc vs Arc) and to enable shared memoization semantics for lazy evaluation.
The pointer and coercion traits are organized as independent traits rather than a linear supertrait chain. Supertrait links exist only where a trait's methods reference the supertrait's associated type:
---
config:
layout: elk
---
graph TD
Pointer["Pointer (Deref)"] -.independent.-> RefCountedPointer["RefCountedPointer (Clone + Deref)"]
Pointer -.independent.-> SendRefCountedPointer["SendRefCountedPointer (Clone + Send + Sync + Deref)"]
RefCountedPointer --> ToDynCloneFn["ToDynCloneFn"]
SendRefCountedPointer --> ToDynSendFn["ToDynSendFn"]
Pointer --> ToDynFn["ToDynFn"]
Pointer --> ToDynFnOnce["ToDynFnOnce"]
BoxBrand -.implements.-> Pointer
BoxBrand -.implements.-> ToDynFn
BoxBrand -.implements.-> ToDynFnOnce
RcBrand -.implements.-> Pointer
RcBrand -.implements.-> RefCountedPointer
RcBrand -.implements.-> ToDynFn
RcBrand -.implements.-> ToDynCloneFn
ArcBrand -.implements.-> Pointer
ArcBrand -.implements.-> RefCountedPointer
ArcBrand -.implements.-> SendRefCountedPointer
ArcBrand -.implements.-> ToDynFn
ArcBrand -.implements.-> ToDynCloneFn
ArcBrand -.implements.-> ToDynSendFn
| Trait | Associated type | Bounds | Purpose |
|---|---|---|---|
Pointer |
Of |
Deref |
Heap-allocated pointer |
RefCountedPointer |
Of, TakeCellOf |
Clone + Deref |
Clonable reference-counted pointer |
SendRefCountedPointer |
Of |
Clone + Send + Sync + Deref |
Thread-safe reference-counted pointer |
ToDynFn |
(uses Pointer::Of) |
Coerce impl Fn -> dyn Fn behind a pointer |
|
ToDynFnOnce |
(uses Pointer::Of) |
Coerce impl FnOnce -> dyn FnOnce behind a pointer (single-shot) |
|
ToDynCloneFn |
(uses RefCountedPointer::Of) |
Coerce impl Fn -> dyn Fn behind a clonable pointer |
|
ToDynSendFn |
(uses SendRefCountedPointer::Of) |
Coerce impl Fn -> dyn Fn + Send + Sync behind a thread-safe pointer |
| Brand | Implements |
|---|---|
BoxBrand |
Pointer, ToDynFn, ToDynFnOnce |
RcBrand |
Pointer, RefCountedPointer, ToDynFn, ToDynCloneFn |
ArcBrand |
All six multi-shot traits (Pointer, RefCountedPointer, SendRefCountedPointer, ToDynFn, ToDynCloneFn, ToDynSendFn); does NOT implement ToDynFnOnce |
The (pointer-capability, closure-semantic) matrix maps each pair to exactly the brands that legitimately host it:
| Closure semantic | Owned (BoxBrand) |
Refcounted (RcBrand) |
Thread-safe refcounted (ArcBrand) |
|---|---|---|---|
Fn (multi-shot) |
ToDynFn |
ToDynFn + ToDynCloneFn |
All four Fn-family traits |
FnOnce (single-shot) |
ToDynFnOnce |
(operationally broken) | (operationally broken) |
Rc<dyn FnOnce> and Arc<dyn FnOnce> cannot be implemented because FnOnce::call_once consumes self (the trait object), which cannot be moved out of a shared pointer without invalidating other clones. The only legitimate dyn FnOnce carrier is Box<dyn FnOnce>, where the standard library's blanket impl<F: ?Sized + FnOnce<Args>> FnOnce<Args> for Box<F> consumes the box on call, moving the underlying FnOnce out and dropping the Box allocation in one step.
Use-case: single-shot continuation cells in effect types such as BoxState, BoxReader, BoxChoose, and default Run scoped-effect closure storage, where the program-tree's single-shot semantics make FnOnce the precise type and Box<dyn Fn> would over-promise multi-shot semantics. Multi-shot wrappers (RcRun / ArcRun and their Explicit siblings) keep using the cloneable dyn Fn paths via ToDynCloneFn / ToDynSendFn.
FnBrand<P> is parameterized over a RefCountedPointer brand P:
RcFnBrandis a type alias forFnBrand<RcBrand>.ArcFnBrandis a type alias forFnBrand<ArcBrand>.
This allows a unified implementation of CloneFn while SendCloneFn is only implemented when P: ToDynSendFn. Both traits are parameterized by ClosureMode (Val or Ref), controlling whether the wrapped closure takes its input by value (Fn(A) -> B) or by reference (Fn(&A) -> B). The composable variant Arrow (for optics) adds Category + Strong supertraits. Code that is generic over the pointer brand can work with either Rc or Arc without duplication.
Lazy uses a configuration trait (LazyConfig) to abstract over the underlying storage and synchronization primitives, ensuring shared memoization semantics across clones:
Lazy<'a, A, Config>is parameterized by aLazyConfigwhich defines the storage type.RcLazyusesRc<LazyCell>for single-threaded, shared memoization.ArcLazyusesArc<LazyLock>for thread-safe, shared memoization.
This ensures Haskell-like semantics where forcing one reference updates the value for all clones. See Lazy Evaluation for the full type overview, trade-offs, and decision guide.
- Correctness: Ensures
Lazybehaves correctly as a shared thunk rather than a value that is re-evaluated per clone. - Performance: Leverages standard library types (
LazyCell,LazyLock) for efficient, correct-by-construction memoization. - Flexibility: Separates the concern of memoization (
Lazy) from computation (Trampoline/Thunk), allowing users to choose the right tool for the job.