Witness tables
A trait-typed value carries a witness table per trait it is known through, so a generic bound is satisfied by any trait the value's own traits require, even without knowing its concrete type.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Calling a generic function normally compiles one copy of it per concrete type. A witness table is what makes the same call work when the argument is only known through a trait, with the type behind it erased.
Example#
fn describe<Value: Show>(value: Value): String {
"{value}"
}
fn report(error: Error): String {
describe error
}
type ConfigError with Error {
}
print report(ConfigError())Syntax#
fn <name><Item: <Bound>>(value: Item): ... a generic bound, checked against whatever `Item` turns out to be
fn <name>(value: <Trait>): ... a trait-typed parameter - `value` carries a witness tableRules#
A trait-typed value carries a witness table: one function pointer per member of the trait it is known through.
error: Errorabove never needs to know it is holding aConfigError- the witness table already points atConfigError's members.A generic call passes one witness per bound, when the argument is trait-typed.
describe errorfillsValuewithErrorand passesError's witness table forShowalong with it, instead of compiling a version ofdescribeforConfigError.A witness table satisfies a bound on any trait the value's own traits require, not only the trait the value is spelled with.
error: ErrorsatisfiesValue: Showabove becausetrait Error with Showalready requires everyErrorto carry aShowwitness -reportnever writesShowanywhere.Kompiliert nichtfn describe<Value: Show>(value: Value): String { "{value}" } trait Loud { fn shout(): String } fn announce(entry: Loud): String { describe(entry) } // error: `Loud` does not implement `Show`Result<Value, Failure>isShowwhenever both are, which is whyResult<Void, Error>isShowwithout writing anything for it.extend<Value: Show, Failure: Show> Result<Value, Failure> with Showandtrait Error with Showcompose: an error handled only asErroris still printable.Where no trait-typed value is involved, a back end monomorphizes as it always did.
describe(5)compiles a version ofdescribespecialized toInt, with no witness table anywhere - witnesses exist only where a concrete type has already been erased to a trait.
What this is not#
A witness table is not something you write or name. There is no syntax for it; it exists because a value's type is a trait, not because a declaration asks for one. Writing the trait bound is all a generic function ever does.
fn describe<Value: Show>(value: Value): String {
"{value}"
}
fn report(error: Error): String {
describe error
}fn describe<Value: Show>(value: Value): String {
"{value}"
}
fn report(error: Error): String {
describe(error, Error)
}
// error: `describe` takes 1 argument, 2 were givendescribe only ever takes the one value; there is no second, explicit argument for a witness to fill, and passing
the trait's own name does not mean what it might in a language with explicit dictionaries. The table travels with
error itself, invisibly.
Related#
- Bounds - what a bound checks; witnesses are how it is checked against a trait-typed value.
- Object safety - the members no witness table can offer, whatever the bound asks.
- Traits as types - what a value becomes once it is known only through a trait.