feat: let observers be shared, boxed, or borrowed

`History` takes its observer by value and never hands it back, so a
caller who wanted to read what an observer recorded had no way to keep a
handle to it. The natural spelling did not compile:

    let recorder = Arc::new(Recorder::default());
    History::builder().observer(Arc::clone(&recorder))
    // error[E0277]: `Arc<Recorder>: Observer<i64>` is not satisfied

The workaround was for every observer to wrap each of its own fields in
an `Arc` and derive `Clone` — one allocation and one lock per field, a
pattern each implementor had to rediscover, and nothing documenting it.

Adds blanket `Observer` impls for `Arc<O>`, `Box<O>` and `&O`. All are
`?Sized`, so `Arc<dyn Observer<T>>` and `Box<dyn Observer<T>>` work too
and an observer can be chosen at runtime. Also adds
`History::observer()` and `into_observer()`, so a non-shared observer's
state can be inspected in place or reclaimed after `converge` without
needing interior mutability at all.

`tests/observer.rs` is simplified to the shared spelling, so the
recommended pattern is the one demonstrated rather than the workaround.

Closes #40

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
2026-09-07 15:13:28 +02:00
co-authored by Claude Opus 5
parent 3c2f9ac64c
commit 2fff745c3b
3 changed files with 166 additions and 13 deletions
+20
View File
@@ -538,6 +538,26 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
.sum()
}
/// The configured observer.
///
/// `History` takes its observer by value, so this is how a caller inspects
/// one it did not keep a handle to. For an observer that accumulates
/// state, prefer passing an `Arc` and keeping a clone — see the
/// [`Observer`] docs.
#[must_use]
pub fn observer(&self) -> &O {
&self.observer
}
/// Consume the history and return its observer.
///
/// Useful for reclaiming a non-shared observer's accumulated state after
/// `converge` without needing interior mutability.
#[must_use]
pub fn into_observer(self) -> O {
self.observer
}
/// Every team's member skills, validated.
///
/// # Errors