History<T: Time, ...> is generic over the time axis, Untimed is exported, and Time is a public trait documented as the extension point. A downstream crate cannot instantiate any T except i64.
Measured
All three routes fail from a consumer crate:
let_h: History<Untimed,ConstantDrift,NullObserver,&'staticstr>=History::builder().build();// error[E0308]: expected `History<Untimed, ...>`, found `History<i64, ConstantDrift, ...>`
let_a: History<Untimed>=History::default();// error[E0308]: same
let_b=HistoryBuilder::<Untimed,ConstantDrift,NullObserver,String>::default().build();// error[E0599]: no associated function `default` found for `HistoryBuilder<T, D, O, K>`
History::builder_with_key() also returns HistoryBuilder<i64, ...> (:356) — it generalises K only
HistoryBuilder's fields are private, it has no new(), and its only Default impl is for the i64 instantiation (:221)
HistoryBuilder has type-changing methods for D (drift) and O (observer), but none for T.
What that costs
Untimed is inert. Zero uses outside the crate's own unit tests. The README lists it as a shipped feature (a ticked Todo box).
Drift<T>'s generality is inert. The trait is generic over T specifically so "seasonal or calendar-aware drift is expressible without going through i64" — its own words. Nobody can write one.
A consumer with chrono::NaiveDate timestamps must convert to i64 and cannot express their domain type, which is the stated reason the parameter exists.
So one of History's four type parameters is paid for at every signature and cannot be varied. Either it earns its place or it should go.
Fix — two options
A. Make it reachable, consistent with how D and O already work:
Then History::builder().time_type::<Untimed>().key_type::<String>().build() reads the same as .drift(..) and .observer(..), and builder_with_key can go — one entry point instead of a _with_key naming pattern that implies siblings it does not have.
Simpler alternative: a generic HistoryBuilder::new(), or impl Default for HistoryBuilder<T, ConstantDrift, NullObserver, K> generic in T.
B. Remove the generality. Delete Untimed, hard-code i64, drop T from every signature. That is a large simplification — but the README advertises the feature, and drift-over-real-time is the crate's premise, so (A) is the honest fix.
Either way this wants a doctest that actually constructs a non-i64 history, since nothing in the repo does and that is why it went unnoticed.
Breaks: (A) removes builder_with_key. (B) removes Untimed, Time's generality, and a type parameter.
Found by an API audit, 2026-09-09; all three failures reproduced independently.
`History<T: Time, ...>` is generic over the time axis, `Untimed` is exported, and `Time` is a public trait documented as the extension point. **A downstream crate cannot instantiate any `T` except `i64`.**
## Measured
All three routes fail from a consumer crate:
```rust
let _h: History<Untimed, ConstantDrift, NullObserver, &'static str> = History::builder().build();
// error[E0308]: expected `History<Untimed, ...>`, found `History<i64, ConstantDrift, ...>`
let _a: History<Untimed> = History::default();
// error[E0308]: same
let _b = HistoryBuilder::<Untimed, ConstantDrift, NullObserver, String>::default().build();
// error[E0599]: no associated function `default` found for `HistoryBuilder<T, D, O, K>`
```
Because:
- `History::builder()` returns `HistoryBuilder<i64, ...>` (`src/history.rs:348`)
- `History::builder_with_key()` also returns `HistoryBuilder<i64, ...>` (`:356`) — it generalises `K` only
- `HistoryBuilder`'s fields are private, it has no `new()`, and its only `Default` impl is for the `i64` instantiation (`:221`)
`HistoryBuilder` has type-changing methods for `D` (`drift`) and `O` (`observer`), but none for `T`.
## What that costs
- **`Untimed` is inert.** Zero uses outside the crate's own unit tests. The README lists it as a shipped feature (a ticked Todo box).
- **`Drift<T>`'s generality is inert.** The trait is generic over `T` specifically so "seasonal or calendar-aware drift is expressible without going through `i64`" — its own words. Nobody can write one.
- A consumer with `chrono::NaiveDate` timestamps must convert to `i64` and cannot express their domain type, which is the stated reason the parameter exists.
So one of `History`'s four type parameters is paid for at every signature and cannot be varied. Either it earns its place or it should go.
## Fix — two options
**A. Make it reachable**, consistent with how `D` and `O` already work:
```rust
impl<T, D, O, K> HistoryBuilder<T, D, O, K> {
pub fn key_type<K2: Eq + Hash + Clone>(self) -> HistoryBuilder<T, D, O, K2>;
pub fn time_type<T2: Time>(self) -> HistoryBuilder<T2, D, O, K>;
}
```
Then `History::builder().time_type::<Untimed>().key_type::<String>().build()` reads the same as `.drift(..)` and `.observer(..)`, and `builder_with_key` can go — one entry point instead of a `_with_key` naming pattern that implies siblings it does not have.
Simpler alternative: a generic `HistoryBuilder::new()`, or `impl Default for HistoryBuilder<T, ConstantDrift, NullObserver, K>` generic in `T`.
**B. Remove the generality.** Delete `Untimed`, hard-code `i64`, drop `T` from every signature. That is a large simplification — but the README advertises the feature, and drift-over-real-time is the crate's premise, so (A) is the honest fix.
Either way this wants a doctest that actually constructs a non-`i64` history, since nothing in the repo does and that is why it went unnoticed.
**Breaks:** (A) removes `builder_with_key`. (B) removes `Untimed`, `Time`'s generality, and a type parameter.
Found by an API audit, 2026-09-09; all three failures reproduced independently.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
History<T: Time, ...>is generic over the time axis,Untimedis exported, andTimeis a public trait documented as the extension point. A downstream crate cannot instantiate anyTexcepti64.Measured
All three routes fail from a consumer crate:
Because:
History::builder()returnsHistoryBuilder<i64, ...>(src/history.rs:348)History::builder_with_key()also returnsHistoryBuilder<i64, ...>(:356) — it generalisesKonlyHistoryBuilder's fields are private, it has nonew(), and its onlyDefaultimpl is for thei64instantiation (:221)HistoryBuilderhas type-changing methods forD(drift) andO(observer), but none forT.What that costs
Untimedis inert. Zero uses outside the crate's own unit tests. The README lists it as a shipped feature (a ticked Todo box).Drift<T>'s generality is inert. The trait is generic overTspecifically so "seasonal or calendar-aware drift is expressible without going throughi64" — its own words. Nobody can write one.chrono::NaiveDatetimestamps must convert toi64and cannot express their domain type, which is the stated reason the parameter exists.So one of
History's four type parameters is paid for at every signature and cannot be varied. Either it earns its place or it should go.Fix — two options
A. Make it reachable, consistent with how
DandOalready work:Then
History::builder().time_type::<Untimed>().key_type::<String>().build()reads the same as.drift(..)and.observer(..), andbuilder_with_keycan go — one entry point instead of a_with_keynaming pattern that implies siblings it does not have.Simpler alternative: a generic
HistoryBuilder::new(), orimpl Default for HistoryBuilder<T, ConstantDrift, NullObserver, K>generic inT.B. Remove the generality. Delete
Untimed, hard-codei64, dropTfrom every signature. That is a large simplification — but the README advertises the feature, and drift-over-real-time is the crate's premise, so (A) is the honest fix.Either way this wants a doctest that actually constructs a non-
i64history, since nothing in the repo does and that is why it went unnoticed.Breaks: (A) removes
builder_with_key. (B) removesUntimed,Time's generality, and a type parameter.Found by an API audit, 2026-09-09; all three failures reproduced independently.