A consumer cannot derive Debug on anything holding a History:
#[derive(Debug)]structMyApp{history: History}
error[E0277]: `trueskill_tt::History` doesn't implement `Debug`
5 | history: History,
| ^^^^^^^^^^^^^^^^ the trait `Debug` is not implemented
Both known consumers hold a History in application state, so both hit this. HistoryBuilder has the same gap while deriving Clone.
The survey
Measured from a consumer crate:
Type
Derives
Missing
History
(none) — Send, Sync, Default are impl'd
Debug, Clone
HistoryBuilder
Clone
Debug
Joint
manual Debug
—
Gaussian
Clone, Copy, PartialEq, Debug
—
Rating
Clone, Copy, Debug
PartialEq
Event / Team / Member
Clone, Debug
PartialEq
ConvergenceReport
Clone, Debug
PartialEq
Outcome
Clone, Debug, PartialEq
—
GameOptions, ConvergenceOptions
Clone, Copy, Debug + Default
—
InferenceError
Debug, Clone, PartialEq
—
Send + Sync + Default on History are all present, which is what a server needs — so this is a gap rather than a deliberate pattern.
The inconsistencies
Gaussian has PartialEq but Rating does not, though both are pure value/config types and Rating is just a Gaussian plus three scalars. Comparing two ratings is a natural thing to want.
Event/Team/Member are input value types with no way to compare them — which makes ingestion round-trip tests awkward for consumers.
HistoryBuilder has Clone but not Debug, the opposite of the usual pairing.
Suggested
Debug on History and HistoryBuilder. History's should be hand-written and summarising — competitor count, slice count, whether converged — not a dump of every skill store, in the same spirit as the manual Debug already written for Joint (which deliberately omits the n² factorisation).
PartialEq on Rating, Event, Team, Member, ConvergenceReport.
Clone on History is a judgement call — it is expensive and rarely wanted; leaving it off is defensible, but worth being a decision rather than an omission.
Breaks: nothing. All additive.
Found by an API audit, 2026-09-09; the Debug failure reproduced from a consumer crate.
## The concrete cost
A consumer cannot derive `Debug` on anything holding a `History`:
```rust
#[derive(Debug)]
struct MyApp { history: History }
```
```
error[E0277]: `trueskill_tt::History` doesn't implement `Debug`
5 | history: History,
| ^^^^^^^^^^^^^^^^ the trait `Debug` is not implemented
```
Both known consumers hold a `History` in application state, so both hit this. `HistoryBuilder` has the same gap while deriving `Clone`.
## The survey
Measured from a consumer crate:
| Type | Derives | Missing |
|---|---|---|
| `History` | *(none)* — `Send`, `Sync`, `Default` are impl'd | **`Debug`**, `Clone` |
| `HistoryBuilder` | `Clone` | **`Debug`** |
| `Joint` | manual `Debug` | — |
| `Gaussian` | `Clone, Copy, PartialEq, Debug` | — |
| `Rating` | `Clone, Copy, Debug` | **`PartialEq`** |
| `Event` / `Team` / `Member` | `Clone, Debug` | **`PartialEq`** |
| `ConvergenceReport` | `Clone, Debug` | `PartialEq` |
| `Outcome` | `Clone, Debug, PartialEq` | — |
| `GameOptions`, `ConvergenceOptions` | `Clone, Copy, Debug` + `Default` | — |
| `InferenceError` | `Debug, Clone, PartialEq` | — |
`Send + Sync + Default` on `History` are all present, which is what a server needs — so this is a gap rather than a deliberate pattern.
## The inconsistencies
- **`Gaussian` has `PartialEq` but `Rating` does not**, though both are pure value/config types and `Rating` is just a `Gaussian` plus three scalars. Comparing two ratings is a natural thing to want.
- **`Event`/`Team`/`Member` are input value types with no way to compare them** — which makes ingestion round-trip tests awkward for consumers.
- **`HistoryBuilder` has `Clone` but not `Debug`**, the opposite of the usual pairing.
## Suggested
- `Debug` on `History` and `HistoryBuilder`. `History`'s should be hand-written and summarising — competitor count, slice count, whether converged — not a dump of every skill store, in the same spirit as the manual `Debug` already written for `Joint` (which deliberately omits the n² factorisation).
- `PartialEq` on `Rating`, `Event`, `Team`, `Member`, `ConvergenceReport`.
- `Clone` on `History` is a judgement call — it is expensive and rarely wanted; leaving it off is defensible, but worth being a decision rather than an omission.
**Breaks:** nothing. All additive.
Found by an API audit, 2026-09-09; the `Debug` failure reproduced from a consumer crate.
Closed by 7ca0daa (merged as 9d629d0), together with earlier api/cleanup work.
Already landed before this commit: hand-written summarising Debug for History (competitor count, event count, slice count, the configured priors — not a dump of the skill stores, matching what Joint's manual Debug does with its n² factorisation), Debug on HistoryBuilder, and PartialEq on Rating, Event, Team, Member, ConvergenceReport.
One correction to the survey.Rating derived PartialEq — and it was unusable. The derive puts a D: PartialEq bound on the impl, and ConstantDrift, the crate's own only Drift implementation, did not satisfy it:
error[E0369]: binary operation `==` cannot be applied to type `Rating`
| pub struct ConstantDrift(f64);
| ------------------------ `trueskill_tt::ConstantDrift` is defined in another crate
Reading the derive list said this was done; writing the comparison from a consumer's position said otherwise. ConstantDrift now derives it, as do ConvergenceOptions and GameOptions — all three are pure configuration and nothing about them makes equality ambiguous.
tests/trait_impls.rs pins the surface, written in the shape the failure was reported (a consumer struct holding a History), and asserts History's Debug output summarises rather than dumps — so a future #[derive(Debug)] cannot quietly replace the manual impl.
Clone on History stays off, as a decision rather than an omission: a history owns every slice's skill store and scratch arena, so a clone costs the whole fit, and neither consumer has wanted one. Reopen this if that changes.
Closed by 7ca0daa (merged as 9d629d0), together with earlier `api/cleanup` work.
Already landed before this commit: hand-written summarising `Debug` for `History` (competitor count, event count, slice count, the configured priors — not a dump of the skill stores, matching what `Joint`'s manual `Debug` does with its n² factorisation), `Debug` on `HistoryBuilder`, and `PartialEq` on `Rating`, `Event`, `Team`, `Member`, `ConvergenceReport`.
**One correction to the survey.** `Rating` derived `PartialEq` — and it was unusable. The derive puts a `D: PartialEq` bound on the impl, and `ConstantDrift`, the crate's own only `Drift` implementation, did not satisfy it:
```
error[E0369]: binary operation `==` cannot be applied to type `Rating`
| pub struct ConstantDrift(f64);
| ------------------------ `trueskill_tt::ConstantDrift` is defined in another crate
```
Reading the derive list said this was done; writing the comparison from a consumer's position said otherwise. `ConstantDrift` now derives it, as do `ConvergenceOptions` and `GameOptions` — all three are pure configuration and nothing about them makes equality ambiguous.
`tests/trait_impls.rs` pins the surface, written in the shape the failure was reported (a consumer struct *holding* a `History`), and asserts `History`'s `Debug` output summarises rather than dumps — so a future `#[derive(Debug)]` cannot quietly replace the manual impl.
**`Clone` on `History` stays off**, as a decision rather than an omission: a history owns every slice's skill store and scratch arena, so a clone costs the whole fit, and neither consumer has wanted one. Reopen this if that changes.
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.
The concrete cost
A consumer cannot derive
Debugon anything holding aHistory:Both known consumers hold a
Historyin application state, so both hit this.HistoryBuilderhas the same gap while derivingClone.The survey
Measured from a consumer crate:
HistorySend,Sync,Defaultare impl'dDebug,CloneHistoryBuilderCloneDebugJointDebugGaussianClone, Copy, PartialEq, DebugRatingClone, Copy, DebugPartialEqEvent/Team/MemberClone, DebugPartialEqConvergenceReportClone, DebugPartialEqOutcomeClone, Debug, PartialEqGameOptions,ConvergenceOptionsClone, Copy, Debug+DefaultInferenceErrorDebug, Clone, PartialEqSend + Sync + DefaultonHistoryare all present, which is what a server needs — so this is a gap rather than a deliberate pattern.The inconsistencies
GaussianhasPartialEqbutRatingdoes not, though both are pure value/config types andRatingis just aGaussianplus three scalars. Comparing two ratings is a natural thing to want.Event/Team/Memberare input value types with no way to compare them — which makes ingestion round-trip tests awkward for consumers.HistoryBuilderhasClonebut notDebug, the opposite of the usual pairing.Suggested
DebugonHistoryandHistoryBuilder.History's should be hand-written and summarising — competitor count, slice count, whether converged — not a dump of every skill store, in the same spirit as the manualDebugalready written forJoint(which deliberately omits the n² factorisation).PartialEqonRating,Event,Team,Member,ConvergenceReport.CloneonHistoryis a judgement call — it is expensive and rarely wanted; leaving it off is defensible, but worth being a decision rather than an omission.Breaks: nothing. All additive.
Found by an API audit, 2026-09-09; the
Debugfailure reproduced from a consumer crate.Closed by
7ca0daa(merged as9d629d0), together with earlierapi/cleanupwork.Already landed before this commit: hand-written summarising
DebugforHistory(competitor count, event count, slice count, the configured priors — not a dump of the skill stores, matching whatJoint's manualDebugdoes with its n² factorisation),DebugonHistoryBuilder, andPartialEqonRating,Event,Team,Member,ConvergenceReport.One correction to the survey.
RatingderivedPartialEq— and it was unusable. The derive puts aD: PartialEqbound on the impl, andConstantDrift, the crate's own onlyDriftimplementation, did not satisfy it:Reading the derive list said this was done; writing the comparison from a consumer's position said otherwise.
ConstantDriftnow derives it, as doConvergenceOptionsandGameOptions— all three are pure configuration and nothing about them makes equality ambiguous.tests/trait_impls.rspins the surface, written in the shape the failure was reported (a consumer struct holding aHistory), and assertsHistory'sDebugoutput summarises rather than dumps — so a future#[derive(Debug)]cannot quietly replace the manual impl.CloneonHistorystays off, as a decision rather than an omission: a history owns every slice's skill store and scratch arena, so a clone costs the whole fit, and neither consumer has wanted one. Reopen this if that changes.