hd-lang

#Traits

Status: language specification draft.

This chapter defines traits, their implementations, and how trait methods are selected.

  1. trait.kind.sharedTraits describe behavior shared by otherwise unrelated types.
  2. trait.kind.explicitConformance is explicit.
  3. trait.kind.no-structuralhd-lang does not use structural method matching or class inheritance.
trait Greet:
    fn greet(self) -> string

struct User:
    name: string

impl Greet for User:
    fn greet(self) -> string:
        "hi, ${self.name}"

#Trait Declarations

A trait declares required methods:

trait Describe:
    fn describe(self) -> string

A method with a body is a default implementation:

trait Named:
    fn name(self) -> string

    fn label(self) -> string:
        "name: " + self.name()

#Trait Members

  1. trait.decl.requiredA trait declares required methods.
  2. trait.decl.defaultA method with a body is a default implementation.
  3. trait.decl.uniqueMember names must be unique within a trait. A repeated associated type, method, or associated function name is an error, in a trait or in an implementation of one. Error: duplicate-trait-member.
  4. trait.decl.typedEvery parameter and result type is explicit; omitting one is an error. Error: syntax-error.
  5. trait.decl.method-receiverA function whose first parameter is a receiver, self, mut self, &self, or &mut self, is a method.
  6. trait.decl.mut-receiverA mutable receiver is a requirement callers must satisfy.
  7. trait.decl.associated-fnA function without a receiver is an associated function. It is called with qualified Trait::function(...) or Type::function(...) syntax.
Error example
trait Named:
    fn name(self) -> string
    fn name(self) -> string  # error: duplicate-trait-member

#Default Method Bodies

  1. trait.default.checked-onceA default method body is checked once, in the trait declaration, with Self bounded by the trait.
  2. trait.default.visibleThrough self and Self, the body sees only the members of the trait and of its transitive supertraits.
  3. trait.default.no-self-membersThe fields and inherent methods of an implementing type are not accessible there, even when every implementing type declares them. Naming one there is an error. Error: unknown-method.
  4. trait.default.no-self-members.errorA call there of a method that neither the trait nor a supertrait declares is an error. Error: unknown-method.
  5. trait.default.not-per-implA default body is not checked again for an implementation, so a bound that an implementation's method adds does not make a call in the default fail, as in annot.walk.rest.default.
Error example
trait Greeter:
    fn name(self) -> string

    fn greet(self) -> string:
        "hi " + self.name()

    fn shout(self) -> string:
        self.loud_name()  # error: unknown-method

    fn tag(self) -> string:
        self.email  # error: unknown-method

See also: Supertraits.

#Generic Traits

Traits may be generic:

trait Add[T]:
    fn add(self, other: T) -> T
  1. trait.decl.genericTraits may be generic.
  2. trait.decl.generic.invariantA trait's generic parameters are invariant, so Source[*mut User] and Source[*User] are unrelated trait instantiations. Using one where the other is required is an error. Error: type-mismatch.
  3. trait.decl.generic.no-varianceA variance marker on a trait's generic parameter is an error. Error: invalid-variance.
  4. trait.decl.generic.defaultA trait's generic parameter may declare a default, which may name Self, as in trait Same[Other = Self]. Self there is the implementing type or the bounded type.
Error example
trait Source[+T]:  # error: invalid-variance
    fn source(self) -> T

See also: Variance.

#Marker Traits

A trait with no methods is a marker, written without a body:

trait Serializable

impl Serializable for User
  1. trait.marker.declA trait with no methods may be declared as a marker without a body.
  2. trait.marker.implThe marker's implementation likewise has no body.
  3. trait.marker.all-defaultsA bodyless implementation is also permitted when every method of the trait has a default.

See also: Embedding And Trait Satisfaction.

#Supertraits

A trait may require another trait using a supertrait bound:

trait Formattable < Display:
    fn format(self) -> string
  1. trait.super.boundA trait may require another trait using a supertrait bound.
  2. trait.super.satisfyAn implementation of Formattable must also satisfy Display.
  3. trait.super.missingAn impl Child for X for which X has no implementation of a supertrait of Child is an error. Error: missing-supertrait-implementation.
  4. trait.super.impl-boundsThe check is made at the implementation, under its own bounds: they must prove every supertrait for its target. With impl[T < Eq] Parent for Box[T], an impl[T] Child for Box[T] is an error. Error: missing-supertrait-implementation.
  5. trait.super.acyclicThe supertrait graph must be acyclic: a direct or indirect cycle is a compile-time error. Error: supertrait-cycle.
  6. trait.super.cycle-reportAn indirect cycle is reported once, on the member of the cycle that appears first. Members are ordered first by module identity, then by source position within the module.
  7. trait.super.uniqueA supertrait bound must not list one trait twice with the same arguments; Convert[i32] & Convert[string] lists two bounds. Error: duplicate-supertrait.
Error example
trait Parent

trait Child < Parent:
    fn value(self) -> i32

struct Item: pass

impl Child for Item:  # error: missing-supertrait-implementation
    fn value(self) -> i32: 42

struct Box[T]:
    value: T

impl[T < Eq] Parent for Box[T]

impl[T] Child for Box[T]:  # error: missing-supertrait-implementation
    fn value(self) -> i32: 0

trait Loop < Loop:  # error: supertrait-cycle
    fn step(self) -> void

trait Twice < Parent & Parent  # error: duplicate-supertrait

#Supertrait Member Names

  1. trait.super.no-redeclareA child trait must not declare a member whose name is also the name of a member of any of its transitive supertraits. Error: duplicate-trait-member.
  2. trait.super.no-redeclare.whenThe check is made at the child trait's declaration, whether or not any type implements it.
  3. trait.super.no-redeclare.kindsThe check applies to associated types, methods, and associated functions alike.
  4. trait.super.no-redeclare.reportThe duplicate-trait-member error is reported on the child's member.
  5. trait.super.no-redeclare.exampleFor example, when Greeter declares fn greet(self) -> string, trait Loud < Greeter must not declare greet, with or without a body.
  6. trait.super.no-default-overrideA child trait therefore cannot provide a default body for a supertrait's method.
  7. trait.super.defaultsA supertrait's defaults come only from the supertrait.
Error example
trait Greeter:
    fn greet(self) -> string

trait Loud < Greeter:
    fn greet(self) -> string  # error: duplicate-trait-member

#Associated Types

Traits may declare associated types, and implementations bind them:

trait Supplier:
    type Item
    fn get(&mut self) -> Self::Item?

impl Supplier for NameSupplier:
    type Item = string

    fn get(&mut self) -> string?:
        ...
  1. trait.assoc.declareTraits may declare associated types, and implementations bind them.
  2. trait.assoc.selfSelf::Item projects from the current implementation.
  3. trait.assoc.projectionT::Item projects from a generic type whose bounds select exactly one associated type declaration.
  4. trait.assoc.ambiguous-typeA projection whose bounds select two different associated type declarations is ambiguous. An ambiguous projection is an error. Error: ambiguous-associated-type.
  5. trait.assoc.no-typeA projection whose bounds select no associated type declaration is an error. Error: unknown-associated-type.
  6. trait.assoc.boundA bound may also fix a projection to a type.
  7. trait.assoc.bound-declA trait's associated type may name bounds after <, joined by &, as in type IntoIter < Iterator[T]. A bound may use the trait's parameters and Self.
  8. trait.assoc.bound-implThe type an implementation binds must implement each bound, with the trait's arguments and the implementing type substituted. A type that does not is an error. Error: unsatisfied-trait-bound.
  9. trait.assoc.bound-useUnder a bound I < Tr[...], the projection I::X implements each bound that Tr declares on X. So its methods are callable, and it satisfies a bound that names one of them.
Error example
trait Named:
    type Item

trait Keyed:
    type Item

fn pick[I < Named & Keyed](value: I) -> I::Item:  # error: ambiguous-associated-type
    panic("unreachable")
Error example
trait Bag[T]:
    type Cursor < Iterator[T]
    fn cursor(self) -> Self::Cursor

fn total[B < Bag[i32]](bag: B) -> i32:
    bag.cursor().sum::[i32]()

struct Words:
    text: string

impl Bag[i32] for Words:  # error: unsatisfied-trait-bound
    type Cursor = string
    fn cursor(self) -> string:
        self.text

See also: Associated Type Bindings.

#Trait Implementations

This section defines the standard comparison, conversion, error, and debug traits, and the rules that every trait implementation follows.

#Comparison Traits

The standard library defines Eq, PartialOrd, Ord, and Ordering in std.cmp.

Rule Trait Requires
trait.cmp.equalityEquality Eq fn eq(self, other: Self) -> bool
trait.cmp.partial-orderingPartial ordering PartialOrd < Eq fn partial_cmp(self, other: Self) -> Ordering?, where .None means unordered
trait.cmp.total-orderingOrdering Ord < PartialOrd fn cmp(self, other: Self) -> Ordering
  1. trait.cmp.std-moduleThe standard library defines Eq, PartialOrd, Ord, and Ordering in std.cmp.
  2. trait.cmp.orderingOrdering has Less, Equal, and Greater cases.
  3. trait.cmp.one-eqEq is the only equality trait. There is no separate partial-equality trait.
  4. trait.cmp.reflexiveAn Eq implementation must be reflexive: x.eq(x) is true for every value x.
  5. trait.cmp.ord-agreeAn Ord implementation must agree with the type's PartialOrd implementation.
  6. trait.cmp.lawsThese semantic laws are obligations of the implementer; ordinary trait checking cannot prove them.
  7. trait.cmp.float-eqFloating-point types implement Eq with IEEE 754 semantics, so NaN is unequal even to itself. This is the one documented exception to the reflexive law.
  8. trait.cmp.float-ordFloating-point types implement PartialOrd but not Ord, because NaN is unordered.
  9. trait.cmp.operators-comparison==, !=, and the relational operators invoke the comparison traits. The other operators invoke the std.ops traits of Operator Traits.
@derive(Eq, PartialOrd, Ord)
struct Level:
    rank: i32

fn demo(a: Level, b: Level) -> bool:
    a < b

Why. This follows Swift's Equatable: one equality trait serves ==, map keys, and derivation, and floats keep IEEE 754 equality.

#Hashing

The standard library also defines Hash and Hasher in std.hash:

trait Hasher:
    fn write(&mut self, bytes: List[u8]) -> void
    fn write_u8(&mut self, value: u8) -> void
    fn write_u16(&mut self, value: u16) -> void
    fn write_u32(&mut self, value: u32) -> void
    fn write_u64(&mut self, value: u64) -> void
    fn write_usize(&mut self, value: usize) -> void
    fn write_i8(&mut self, value: i8) -> void
    fn write_i16(&mut self, value: i16) -> void
    fn write_i32(&mut self, value: i32) -> void
    fn write_i64(&mut self, value: i64) -> void
    fn write_str(&mut self, text: string) -> void

trait Hash:
    fn hash(self, state: &mut dyn Hasher) -> void
  1. trait.hash.moduleThe standard library defines Hash and Hasher in std.hash.
  2. trait.hash.map-keyA map key must implement both Eq and Hash.
  3. trait.hash.not-inferredNeither trait is inferred for user-defined structs or enums, including payload-free enums.
  4. trait.hash.unverifiedThe compiler does not verify any relationship between their implementations.
  5. trait.hash.write-requiredwrite is the only required method of Hasher.
  6. trait.hash.typed-methodsHasher supplies default methods write_u8, write_u16, write_u32, write_u64, write_usize, write_i8, write_i16, write_i32, write_i64 and write_str over write.
  7. trait.hash.typed-bytesEach integer method writes the value's bytes least significant first, using two's-complement representation for signed values. Widths are one byte for i8 and u8, two for i16 and u16, four for i32, u32 and usize, and eight for i64 and u64.
  8. trait.hash.string-byteswrite_str(text) writes the UTF-8 bytes of text, with no length.
  9. trait.hash.override-equivalentAn implementation may override a typed method, but it must give the same result as writing the specified bytes through write.
  10. trait.hash.ref-receiverHasher's methods take &mut self, and Hash.hash receives its hasher as &mut dyn Hasher. So a struct that implements Hasher changes in place through a bound and through a trait value reference, and its caller reads the changed state afterwards.

Note. A key that holds a reference can still change through another reference to the same place, potentially leaving its map entry unreachable.

#Derived Implementations

  1. trait.derive.no-automaticThere is no automatic conformance for user-defined struct or enum types.
  2. trait.derive.explicit-implAn explicit implementation may choose domain-specific equality or ordering.
  3. trait.derive.intrinsic-decl@derive(...), as in @derive(Eq, Hash), is an explicit compiler intrinsic on a struct, enum, or newtype declaration.
  4. trait.derive.argumentsIts arguments name traits, not metadata values.
  5. trait.derive.generateThe compiler generates ordinary implementations of the named traits from the declaration's shape.
  6. trait.derive.checkThe compiler checks trait requirements and coherence, and rejects traits for which it has no derivation rule.
  7. trait.derive.boundsFor each derived trait, the generated implementation adds a T < Trait bound for some declaration type parameters. That is every T that occurs in a field compared, ordered, or hashed by that derivation.
  8. trait.derive.bounds.exampleThus @derive(Eq) struct Box[T] produces conformance only when T < Eq.
  9. trait.derive.supertraitsThe target must also satisfy each derived trait's supertraits, whether through an existing implementation or another derivation.
  10. trait.derive.cmp-templatesEq, PartialOrd, Ord, and Hash derive through their templates, as every derivable trait does. The compiler writes none of their bodies.
  11. trait.derive.cmp-every-memberA derivation of Eq, PartialOrd, Ord, or Hash covers every member, and no member line or derivation block configures it.
  12. trait.derive.templated@derive accepts a trait that has a derivation template, as Typed Derivation defines. Any other trait is an error. Error: underivable-trait.
  13. trait.derive.field-missing-traitA field that a derived Eq, PartialOrd, Ord, or Hash compares, orders, or hashes must implement the derived trait. A field that does not is an error at the field, whose message names the trait and the field. A field that lacks several of the derived traits gets one error, naming the first of them in the @derive list. Error: derive-field-missing-trait.
  14. trait.derive.field-missing-trait.templateFor these four traits, this error replaces the template's member-not-derivable of annot.walker.obligation.error.
  15. trait.derive.bound-unmetA use of a derived implementation whose type argument does not meet a bound that trait.derive.bounds added is an error at the use. One case is == on two Box[fn() -> void] values. Error: missing-derived-bound.
  16. trait.derive.ownerA derived implementation is owned by the module that declares its target, whichever module declares the trait.
  17. trait.derive.beside-writtenDeriving a trait for a type that also has a hand-written implementation of that trait is an error, reported on the @derive line. Error: overlapping-impl.
  18. trait.derive.reference-fieldsA reference field, *T or *mut T, implements Eq and Hash when T does, by Reference Equality. So a derivation of either accepts it then, and a type that refers to itself through references may derive both.
  19. trait.derive.no-cycle-checkA derivation is allowed whether or not the values of its type can form a cycle through references. The compiler checks no cycle.
  20. trait.derive.reference-debugA reference field implements Debug when the type it refers to does, by References. So @derive(Debug) accepts it then, as @derive(Eq) does. PartialOrd and Ord have no implementation for references, so a derivation of either rejects a reference field. Error: derive-field-missing-trait.
Error example
struct Opaque: pass

@derive(Eq, Hash)
struct Key:
    value: Opaque  # error: derive-field-missing-trait

@derive(Eq)
struct Box[T]:
    value: T

fn same(left: Box[fn() -> void], right: Box[fn() -> void]) -> bool:
    left == right  # error: missing-derived-bound
Error example
@derive(Eq)  # error: overlapping-impl
struct Badge:
    text: string

impl Eq for Badge:
    fn eq(self, other: Badge) -> bool:
        self.text == other.text

Note. What each derived implementation compares, orders, or hashes is stdlib tier: Cmp and Hash. The checks above stay in the language tier.

Note. std.ops also declares a template for Default, so @derive(Default) builds each member's default, and an enum marks its default variant with @default (Derived Default).

Note. The reference prototype still writes these four bodies in the compiler. It moves them to the templates once its template lowering no longer re-checks generated source. Programs see no difference.

The comparison and hash traits whose laws relate them are related traits:

Rule Trait Related traits
trait.derive.related.hashHash Hash Eq
trait.derive.related.partial-ordPartial ordering PartialOrd Eq
trait.derive.related.ordOrdering Ord Eq and PartialOrd
  1. trait.derive.related.same-listDeriving Hash, PartialOrd, or Ord requires each of its related traits to be derived in the same @derive list.
  2. trait.derive.related.no-mixOne type's related traits must be all derived or all hand-written: a derived implementation and a hand-written implementation of two related traits never coexist.
  3. trait.derive.related.errorA derivation that breaks either rule is an error, reported on its @derive line. Error: mixed-derived-law.
  4. trait.derive.related.replacesWhen a derived trait's missing related trait is also its supertrait, as PartialOrd is for Ord, this error replaces missing-supertrait-implementation on that @derive line.
Error example
@derive(Hash)  # error: mixed-derived-law
struct Session:
    token: string

impl Eq for Session:
    fn eq(self, other: Session) -> bool: self.token == other.token

Why. A hand-written Eq that ignores a field, beside a derived Hash that hashes it, would put equal keys in different buckets.

#Derived Newtypes

  1. trait.derive.newtype@derive on a newtype declaration generates each named trait's implementation from the base type's implementation.
  2. trait.derive.newtype.baseThe generated method applies the base type's method to the wrapped values, as in Mile(1) == Mile(1) comparing the two i32 values.
  3. trait.derive.newtype.requiresThe base type must implement each derived trait, as a derived field must.
  4. trait.derive.newtype.requires.errorA base type that does not implement a derived trait is an error at the base type. The message names the trait and the base type. Error: derive-field-missing-trait.
  5. trait.derive.newtype.not-inheritedA newtype still inherits no implementation it does not derive or implement.
  6. trait.derive.newtype.templatedA trait with a template also derives through the base type: the newtype gets no Structure, and the base type's implementation is rewrapped.
  7. trait.derive.newtype.self-positionsForwarding is allowed only where the trait's methods use Self as the receiver, as plain Self, or inside Self?, Result[Self, E], or List[Self].
  8. trait.derive.newtype.self-errorAny other position, such as Map[Self, V], Map[string, Self], or a tuple holding Self, is an error at the @derive line that names the trait method. Error: newtype-derivation-self.
@derive(Eq, Hash)
type Mile(i32)

fn same(left: Mile, right: Mile) -> bool:
    left == right
Error example
trait Pairing:
    fn pair(self) -> (Self, i32)

@derive(Pairing)  # error: newtype-derivation-self
type Tag(string)

struct Opaque: pass

@derive(Eq)
type Wrapped(Opaque)  # error: derive-field-missing-trait

Why. A Map[Self, V] result depends on the key's own Hash and Eq, which a rewrapped base value would not use. The allowed positions rewrap each value one at a time.

See also: Newtypes.

#Derived Equality

Note. This heading keeps its name so that links to it stay valid. What a derived Eq compares is stdlib tier since batch 36: Derived Equality in std.cmp.

#Derived Ordering

Note. This heading keeps its name so that links to it stay valid. What a derived PartialOrd or Ord compares is stdlib tier since batch 36: Derived Ordering in std.cmp.

#Derived Hashing

Note. This heading keeps its name so that links to it stay valid. What a derived Hash hashes is stdlib tier since batch 36: Derived Hashing in std.hash.

#Conversion Trait

The standard library declares the general conversion trait From in std.convert:

trait From[T]:
    fn from(value: T) -> Self
  1. trait.from.moduleThe standard library declares the general conversion trait From in std.convert.
  2. trait.from.meaningAn implementation impl From[T] for X converts a T into an X.
  3. trait.from.importFrom is not a prelude name; code that names it imports it, as in use std.convert.From.
  4. trait.from.propagationPostfix ? uses the trait without an import. When an error is not assignable to the enclosing function's error type, ? calls that error type's From implementation once.
  5. trait.from.directAny code may also call a conversion directly as X::from(value).

See also: Propagation.

#Pure Conversions

  1. trait.from.pureA conversion is pure.
  2. trait.from.rowThe trait method from has the empty requirement row and is not suspending. An implementation method must agree with it on both.
  3. trait.from.signatureAn implementation whose from declares a requirement clause, as in fn from(value: FsError) -> SyncError $ Console, is an error. So is one whose from is suspending, as in fn from!(value: FsError) -> SyncError. Error: trait-method-signature.
  4. trait.from.panicThe body of from may still panic.
Error example
impl From[FsError] for SyncError:
    fn from(value: FsError) -> SyncError $ Console:  # error: trait-method-signature
        println("converting")
        SyncError.Fs(value)

See also: Requirement Rows.

#Conversion Implementations

  1. trait.from.coherenceFrom implementations follow the ordinary rules for implementation targets, ownership, overlap, and uniqueness.
  2. trait.from.ownersimpl From[FsError] for SyncError may be declared by the package that owns SyncError, the package that owns FsError, or the standard library.
  3. trait.from.no-overlapImplementations for different source types never overlap, because their trait arguments differ. One error type may therefore implement both From[FsError] and From[HttpError].
  4. trait.from.reflexiveA reflexive impl[T] From[T] for T is an error. Error: bare-parameter-impl-target.
  5. trait.from.trait-valueA trait value type is never a target, so impl From[FsError] for Error is an error. Error: trait-value-impl-target.
  6. trait.from.instantiationsWhen X implements From at several instantiations, X::from(value) chooses among them by the rule for instantiations of one generic trait in Method Resolution.
  7. trait.from.candidatesEach instantiation is a candidate, and the one that applies to the call is selected.
  8. trait.from.no-applyWhen no instantiation applies, the call is an error. Error: type-mismatch.
use std.convert.From

struct AppError:
    message: string

impl From[i32] for AppError:
    fn from(code: i32) -> AppError:
        AppError { message: "code $code" }

impl From[string] for AppError:
    fn from(text: string) -> AppError:
        AppError { message: text }

fn demo(code: i32) -> AppError:
    AppError::from(code)   # the i32 instantiation is selected

#Error Trait

The standard library declares the standard error trait Error in std.error.

  1. trait.error.moduleThe standard library declares the standard error trait Error in std.error.
  2. trait.error.supertraitsError has Display and the sealed Inspectable as supertraits, as in trait Error < Display & Inspectable.
  3. trait.error.defaultsEvery member Error declares has a default, so an implementation needs no body.
  4. trait.error.completeimpl Error for FsError is complete when the enum FsError implements Display, because the compiler supplies Inspectable for it.
  5. trait.error.not-inspectableAn impl Error whose target is not inspectable, such as a type declared in a block suite, is an error. Error: missing-supertrait-implementation.
  6. trait.error.value-typesA value type may implement Error, as an enum or a newtype over string does.
  7. trait.error.reference-sourceError declares fn source(self) -> (*dyn Error)?, which returns the error that caused this one. Its default returns .None.
  8. trait.error.dyn-methods.reference-valuesstd.error gives &dyn Error the inherent methods chain, root_cause, and find[T < Error], in an impl dyn Error: block beside the trait.
  9. trait.error.dyn-methods.not-membersThey are not members of Error, so an implementation of Error cannot declare or override them.
  10. trait.error.dyn-methods.reference-receiverThey take shared reference receivers, as in let erased: *dyn Error = &failure; erased.find::[FsError]().
  11. trait.error.inherent-recovery.reference-valuesThe inherent impl dyn Error declares the recovery methods of impl dyn Inspectable, with identical reference signatures.
  12. trait.error.std-apiWhat chain, root_cause, and find return is standard-library API.
  13. trait.error.deriveA struct or enum may implement Display and Error through the @error intrinsic, as Error Derivation defines.
  14. trait.error.importError is not a prelude name; code imports it with use std.error.Error.
  15. trait.error.no-inspect-importImplementing Error needs no import of std.inspect.
Error example
use std.error.Error

fn local() -> void:
    enum LocalError:
        Failed

    impl Display for LocalError:
        fn to_string(self) -> string: "failed"

    impl Error for LocalError  # error: missing-supertrait-implementation
    pass

Why. Most errors are enums, which are values without identity, so Error has no AnyRef supertrait. find still works on an erased error, because a method type parameter may take any type argument. As inherent methods of &dyn Error, the chain helpers mean the same thing for every error.

See also: Sealed Traits, Error Derivation, Cause Chain and Root Cause And Find in std.error.

#Erased Errors

  1. trait.error.erased.reference-valuesThe dynamic trait value *dyn Error is the erased application error.
  2. trait.error.result.reference-valuesA Result[T, *dyn Error] holds any error that implements Error. ? reaches it by reference erasure, sharing a reference or copying a value into a new object.
  3. trait.error.convert.reference-valuesA concrete counted error reference converts implicitly to *dyn Error; an ordinary value requires a written &, as Trait Value References specifies.
  4. trait.error.entry-point.error-boundAn erased *dyn Error satisfies Error and its supertraits, so Result[T, *dyn Error] implements Termination when T < Termination.
  5. trait.error.boundary.reference-valuesLike every dynamic trait value, an erased *dyn Error is not boundary-safe and never crosses a registered boundary.
  6. trait.error.convert-first.reference-valuesCode converts an erased *dyn Error explicitly to a boundary-safe error type first.
  7. trait.error.reference-recoveryBecause Error extends Inspectable, a reference to an erased error recovers its concrete struct or enum through its inherent recovery methods.
use std.error.Error

enum FsError:
    NotFound(path: string)

impl Display for FsError:
    fn to_string(self) -> string:
        match self:
            FsError.NotFound(path) => "not found: " + path

impl Error for FsError

fn read_config(path: string) -> Result[string, FsError]:
    .Err(FsError.NotFound(path))

fn load(path: string) -> Result[string, *dyn Error]:
    text := read_config(path)?
    .Ok(text)

See also: Propagation, Dynamic Trait Values, Wasm Boundary, Runtime Type Identity.

#Debug Trait

The standard library declares Debug in std.format, beside Display, to show a value's structure:

trait Debug:
    fn debug(self, out: *mut DebugWriter) -> void

A type usually derives it:

@derive(Eq, Debug)
struct Point:
    x: i32
    y: i32

fn describe(point: Point) -> string:
    debug(point)
  1. trait.debug.modulestd.format declares Debug and DebugWriter. Debug is a prelude name.
  2. trait.debug.method-refDebug declares fn debug(self, out: *mut DebugWriter) -> void, which writes the value's structure through out, a reference to the one writer of the whole value.
  3. trait.debug.writerDebugWriter is the standard structured writer. An implementation describes the value through its builder calls, such as one call per field, rather than raw text.
  4. trait.debug.std-implsstd implements Debug for the primitives, collections, T?, and Result, each when its type arguments implement Debug.
  5. trait.debug.tuplesstd.format declares a tuple template for Debug, so every tuple whose elements implement Debug implements it, at every size.
  6. trait.debug.tuples.restFor a tuple with a rest element, T must implement Debug. Its rest element's items are written inline after the fixed elements. So a value (1, 2, 3, 4) of type (i32, i32, List[i32]...) writes as the tuple (1, 2, 3, 4) of four i32 does.
  7. trait.debug.derive@derive(Debug) derives Debug through its template. The derived implementation walks the declaration's members and writes each one.
  8. trait.debug.not-displayDebug is separate from Display, which stays user-facing text.
  9. trait.debug.writer-importDebugWriter is not a prelude name, so a hand-written implementation imports it, as in use std.format.DebugWriter.

Note. The text that debug returns, the DebugWriter builders, and the builder calls that @derive(Debug) generates are stdlib tier: Format.

See also: Debug Builders in the stdlib tier, Standard Testing, Typed Derivation.

#Literal Suffix Trait

Note. This heading keeps its name so that links to it stay valid. std.ops.LiteralSuffix and its newtype carriers were removed by Literal Suffixes L11 (2026-09-28): a suffix is a function marked @num_suffix, as Literal Suffixes defines.

#Implementation Declarations

An explicit implementation names the trait and target type:

impl Display for User:
    fn to_string(self) -> string:
        self.email
  1. trait.impl.explicitAn explicit implementation names the trait and target type.
  2. trait.impl.requiredThe implementation must write every required method not supplied by a default, and bind every associated type of the trait. Omitting one is an error. Error: missing-trait-method.
  3. trait.impl.fillOnly a method written in the implementation or a trait default fills a trait method.
  4. trait.impl.fill.neverAn inherent method of the target and a method promoted from an embedded field never fill a trait method, required or defaulted, whatever its receiver. So neither makes a body optional.
  5. trait.impl.override-opaqueThe implementation may override a default with the instantiated signature, allowing the opaque-result refinement below.
  6. trait.impl.signatureA mismatched method is an error. Error: trait-method-signature.
  7. trait.impl.extra-methodsAdditional methods do not become part of that trait implementation; place them in an inherent impl instead. A method or associated type that the trait does not declare is an error. Error: extra-trait-member.
  8. trait.impl.uniqueAt most one implementation of the same instantiated trait for the same target type may exist in a resolved program.
Error example
trait Named:
    fn name(self) -> string

struct User: pass
impl Named for User  # error: missing-trait-method

struct Admin: pass
impl Named for Admin:
    fn name(self) -> string: "admin"
    fn shout(self) -> string: "ADMIN"  # error: extra-trait-member

#Opaque Method Results

A trait method may hide its result behind an impl Trait bound.

  1. trait.opaque.impl-resultAn implementation may return a concrete type or its own opaque result in place of the trait method's opaque occurrence.
  2. trait.opaque.impl-boundThe chosen result must satisfy that occurrence's instantiated bound and associated-type bindings. Error: unsatisfied-trait-bound.
  3. trait.opaque.impl-shapeThe remaining result structure must match the trait declaration. Error: trait-method-signature.
  4. trait.opaque.impl-identityEach trait-method opaque occurrence has a static identity determined by the method, implementation target, trait arguments, method arguments and occurrence position.
  5. trait.opaque.generic-boundA caller through a generic bound sees only the opaque occurrence's declared bounds.
  6. trait.opaque.codegen-selectedCode generation resolves the occurrence to the selected implementation's result, using the trait's default body when no override exists.

Why. Different implementations can return different concrete types while generic callers depend only on the written interface.

#Method Generic Parameters

  1. trait.impl.genericsThe exact signature includes the method-level generic parameters.
  2. trait.impl.generics.countAn implementation method declares as many generic parameters as the trait method, and they correspond by position; names may differ.
  3. trait.impl.generics.markersEach parameter keeps the trait method's bounds.
  4. trait.impl.generics.boundsThe same bounds are the same traits, with the same instantiated arguments and associated type bindings, written in the same order.
  5. trait.impl.generics.defaultEach parameter also repeats the trait method's default, instantiated as the bounds are, or has none when the trait method's has none. A missing, extra, or different default is a mismatch.
  6. trait.impl.generics.fixed-boundsAn implementation method therefore cannot add, drop, reorder, weaken, or strengthen a bound. The one exception is the strengthened member bound of a walker, describer, or source, which annot.walker.strengthen-member allows.
  7. trait.impl.generics.errorAny mismatch is an error reported at the implementation method. Error: trait-method-signature.
Error example
trait Show:
    fn show(self) -> string

trait Describe:
    fn describe[T](self, value: T) -> string

struct Describer: pass

impl Describe for Describer:
    fn describe[T < Show](self, value: T) -> string: value.show()  # error: trait-method-signature

#Method Parameter Defaults

  1. trait.impl.param-defaultA trait method's parameter defaults come from the trait declaration.
  2. trait.impl.param-default.callsA call that omits such an argument evaluates the trait's default, whichever body the call selects: the trait's default body or an implementation's override. This holds for a concrete receiver, a generic bound, and a trait value.
  3. trait.impl.param-default.no-restateAn implementation method declares no parameter defaults. A default written there is an error. Error: trait-method-signature.
Error example
trait Check:
    fn limit(self, max: usize = 64) -> usize: max

struct Item: pass
impl Check for Item

struct Strict: pass
impl Check for Strict:
    fn limit(self, max: usize) -> usize: max / 2    # Strict {}.limit() is 32

struct Loose: pass
impl Check for Loose:
    fn limit(self, max: usize = 128) -> usize: max  # error: trait-method-signature

Why. As in Rust, the signature belongs to the trait. A caller through a bound or a trait value sees only the trait's declaration, so a default restated in an implementation could never apply consistently.

#Local Implementations

  1. trait.impl.localAn impl inside an executable block suite is a compile-time declaration.
  2. trait.impl.local.traitA local trait implementation must involve a local trait or a local nominal target type visible at its declaration point.
  3. trait.impl.local.inherentA local inherent implementation must target a local nominal type. Error: local-impl-nonlocal-pair.
  4. trait.impl.local.nonlocalImplementations for a pair of nonlocal types belong at module scope. A local one is an error. Error: local-impl-nonlocal-pair.
  5. trait.impl.local.checksLocal implementations obey the same target, ownership, overlap, and uniqueness checks as module-level implementations.
  6. trait.impl.local.no-secondLexical scope does not permit a second implementation for an existing pair.
  7. trait.impl.local.no-captureLocal methods and local-trait default methods cannot capture enclosing runtime values. Such a capture is an error. Error: unknown-name.
  8. trait.impl.local.lookupTheir methods are available for lookup from the local impl declaration point through its enclosing suite and child scopes, not before or outside that scope.
fn demo() -> string:
    trait Shout:
        fn shout(self) -> string
    struct Whisper:
        text: string
    impl Shout for Whisper:
        fn shout(self) -> string: self.text
    Whisper { text: "hi" }.shout()

#Intrinsic Methods

An intrinsic method is an implementation method whose body the compiler supplies. The standard library writes each primitive operation as one, such as the addition of every number type:

impl[N < Num] Add for N:
    type Out = N
    @intrinsic
    fn add(self, rhs: N) -> N
  1. trait.impl.intrinsicAn implementation method written after the decorator line @intrinsic has no body. It is an intrinsic method, and the compiler supplies its body.
  2. trait.impl.intrinsic.std-onlyOnly the standard library sees the name intrinsic. In any other package, @intrinsic names no binding. Error: unknown-name.
  3. trait.impl.intrinsic.operationEach intrinsic method computes one primitive operation, such as integer addition, for every type that its implementation covers.
  4. trait.impl.intrinsic.rulesEvery other rule for implementation methods applies to an intrinsic method, including its exact signature.
  5. trait.impl.intrinsic.inlineAn implementation may compile a call of an intrinsic method to inline code, such as the machine addition for a + b on two i32 operands. This is an optimization and never changes a result.
Error example
use std.ops.Add

struct Money:
    cents: i64

impl Add for Money:
    type Out = Money
    @intrinsic  # error: unknown-name
    fn add(self, rhs: Money) -> Money

Why. An operator always calls its trait method, so a + b inside Add.add would call itself. An intrinsic method is the base case that the operator traits rest on, written once per operation.

See also: Primitive Implementations, Standard Library Primitives.

#Implementation Targets

The target of every implementation, trait or inherent, starts with a type constructor.

Rule Type constructor Examples
trait.target.declarationDeclared a struct, enum, or newtype declaration User, Box
trait.target.builtinBuilt-in a built-in type constructor i32, string, List, Map
trait.target.tupleTuple a tuple constructor, one per arity (A, B), the two-element tuple constructor applied to A and B
trait.target.function-typeFunction a function type constructor, Fn or SuspendFn fn(i32) -> i32, which is Fn[(i32,), i32, $()], and SuspendFn[Args, O, $ R]
  1. trait.target.constructorThe target of every implementation, trait or inherent, starts with a type constructor from the table above.
  2. trait.target.tuple.validimpl Display for (i32, string) is a valid target.
  3. trait.target.tuple.arityTuples of different arity never share a constructor.
  4. trait.target.optionAn optional target is the prelude enum Option applied to its contained type. impl Validate for string? targets Option[string], and by Overlap it does not overlap an implementation for i32?.
  5. trait.target.argumentsThe constructor's arguments may be any types, including implementation parameters, as in impl[T < Display] Printable for Box[T].
  6. trait.target.bare-parameterA target that is a bare type parameter, as in impl[T] Describe for T, is an error. Error: bare-parameter-impl-target.
  7. trait.target.numeric-familyThe one exception is a standard-library implementation in which every type parameter is bounded by exactly one of Num, Integer, and Float, as in impl[N < Num] Add for N.
  8. trait.target.numeric-family.eachSuch an implementation stands for one implementation per way of replacing each parameter with a type that Sealed Traits lists for its bound.
  9. trait.target.numeric-family.per-typeSo impl[N < Integer] Shl[u32] for N stands for 9 implementations, one per integer type.
  10. trait.target.no-blankethd-lang has no blanket implementations over every type.
  11. trait.target.function-type.validA function type is an ordinary target under the ownership and overlap rules below, so impl Marker for fn(i32) -> i32 is valid in the package that declares Marker.
  12. trait.target.row-argumentA row argument in an implementation head, such as a function type's row, is a row parameter or a concrete row.
  13. trait.target.row-argument.extensionA row argument that lists a row parameter beside other keys, as in Fn[(), i32, $ R + Log], is an error in an implementation head. Error: invalid-impl-target.
  14. trait.target.trait-valueA trait value type is never an implementation target either: Display used as a type names a dynamic trait value, not a type constructor.
  15. trait.target.trait-value.errorimpl Marker for Display and impl Marker for Any are errors. Error: trait-value-impl-target.
  16. trait.target.trait-value.argument.reference-valuesA trait value type may still be a constructor's argument, as in impl Marker for List[*dyn Display].
  17. trait.target.no-mutA target must not be written with an outer mut: impl Marker for mut Counter is an error. Error: mutable-impl-target.
  18. trait.target.templateA derivation template, written impl[T] Trait for T by Structure, is not an implementation, so these target rules do not apply to it. Each derivation it produces is an ordinary implementation for a declared target, as Templates defines.
Error example
trait Describe:
    fn describe(self) -> string

trait Marker

struct Counter:
    value: i32

impl[T] Describe for T:  # error: bare-parameter-impl-target
    fn describe(self) -> string:
        "value"

impl Marker for &dyn Display      # error: trait-value-impl-target
impl Marker for mut Counter  # error: mutable-impl-target

A function type is a target like any other:

use std.function.{Fn, Tuple}

trait Describe:
    fn describe(self) -> string

impl[Args < Tuple, O, $R] Describe for Fn[Args, O, $ R]:
    fn describe(self) -> string:
        "function"

Note. A later revision may add blanket implementations as a compatible extension.

#Derived Tuple Implementations

Tuples derive traits through tuple templates, at every size:

fn compare(a: (i32, string), b: (i32, string)) -> bool:
    a == b || a < b

fn index(counts: Map[(i32, string), i32]) -> i32:
    counts[(1, "a")]
  1. trait.target.tuple.templatesEvery tuple type, with or without a rest element, derives each trait that has a tuple template. It does so when its elements meet that template's obligation.
  2. trait.target.tuple.no-limitNo tuple trait stops at a fixed number of elements.

Note. std declares tuple templates for Eq, PartialOrd, Ord, and Hash (Tuple Comparison, Tuple Hashing), for Debug and Display, and for Default (Standard Implementations). These are the tuple equality of expr.eq.std, the tuple order of expr.ord.std.sequences, and the hashable tuple keys of types.map-key.builtin-hash.

Note. List[T] implements Hash when T does (types.map-key.builtin-hash). So a tuple with a rest element is a hashable map key when its elements and its rest's items are hashable.

#Implementation Ownership

An impl Trait[Args] for Target may be declared only in a package that owns one of these declarations:

Rule Owned declaration
trait.own.traitTrait the trait
trait.own.targetTarget the target's outer type constructor
trait.own.argumentTrait argument the outer type constructor of one of the trait arguments Args
  1. trait.own.ruleAn impl Trait[Args] for Target may be declared only in a package that owns one of the declarations in the table above.
  2. trait.own.orphanAny other trait implementation is an error. Error: orphan-impl.
  3. trait.own.no-orphan-exceptionNo package, including the root application package, has an orphan exception.
  4. trait.own.argument.exampleFor example, the package that declares Money may write impl Add[Money] for i32, because it owns the trait argument Money.
  5. trait.own.bare-parameterThe trait-argument case never applies to a target that is a bare type parameter.
  6. trait.own.aliasesTransparent aliases do not create ownership; nominal newtypes do.
  7. trait.own.stdThe standard library owns primitives, built-in collection type constructors, tuple constructors, and the prelude enums Option and Result.
  8. trait.own.optionalAn implementation for string? therefore needs the package of the trait or of a trait argument. An example is impl Validate for string? in the package that owns Validate.
  9. trait.own.std.functionThe standard library also owns the function type constructors Fn and SuspendFn. An implementation for a function type therefore needs the package of the trait or of a trait argument.
  10. trait.own.inherentAn inherent implementation may be declared only in the package that owns its target nominal type. Error: orphan-impl.
  11. trait.own.inherent.target-formsAn inherent implementation cannot target a tuple, a transparent alias, or a type owned by another package. A dyn type is a target only as Inherent Methods On dyn Types allows.
  12. trait.own.inherent.tuple-aliasAn inherent implementation whose target is a tuple or a transparent alias is an error, whichever package owns the type it names. Error: invalid-impl-target.
  13. trait.own.inherent.stdThe standard library, which owns them, may declare inherent implementations for primitives, built-in collection type constructors, and the prelude enums Option and Result.
  14. trait.own.inherent.std.no-useTheir pub members are found by ordinary member lookup on the receiver's type, so calling one needs no use.
  15. trait.own.inherent.std.no-tupleTuples have no inherent members, including from the standard library; they get only trait implementations. A member that no trait supplies is an error. Error: unknown-method.
  16. trait.own.graphThe compiler must also reject a resolved dependency graph containing duplicate exact implementations. This includes the possible conflict where two owning packages each provide the same pair.
Error example
impl Display for i32:  # error: orphan-impl
    fn to_string(self) -> string:
        "$self"

impl Display for fn() -> i32:  # error: orphan-impl
    fn to_string(self) -> string:
        "function"

impl i32:  # error: orphan-impl
    fn twice(self) -> i32:
        self * 2

impl (i32, i32):  # error: invalid-impl-target
    fn sum(self) -> i32:
        self._0 + self._1

struct User:
    name: string

type Person = User

impl Person:  # error: invalid-impl-target
    fn label(self) -> string:
        self.name

Why. These ownership rules prevent downstream packages from creating globally surprising conformance.

#Implementation Modules

  1. trait.own.module.inherent-targetAn inherent implementation must be declared in the module that declares its target type, except as trait.own.module.inherent.std allows.
  2. trait.own.module.inherent.stdAn inherent implementation that trait.own.inherent.std allows may be declared in any module of the standard library.
  3. trait.own.module.traitA trait implementation must be declared in a module that declares one of three things. They are the trait, the target's outer type constructor, or the outer type constructor of a trait argument that gives its package ownership.
  4. trait.own.module.errorAn implementation declared in any other module of the owning package is an error. Error: nonlocal-impl.
  5. trait.own.module.generatedA derived implementation is generated in the module of its declaration, so it always satisfies these rules.
trait Greet:
    fn greet(self) -> string

struct User:
    name: string

impl Greet for User:   # same module as User: fine
    fn greet(self) -> string: "hi"

Why. A reader of the type's or the trait's module sees every implementation that can answer a call. No distant module of the package can add one.

#Inherent Methods On dyn Types

The module that declares a trait may give its dyn type inherent methods:

Error example
trait Shape:
    fn area(self) -> f64

impl dyn Shape:
    fn double(self) -> f64:
        self.area() * 2.0

struct Square:
    side: f64

impl Shape for Square:
    fn area(self) -> f64: self.side * self.side

fn report(shape: &dyn Shape, square: Square) -> f64:
    shape.double() + square.double()  # error: unknown-method
  1. trait.own.dyn-inherent.formAn inherent implementation may target a dyn type, as in impl dyn Shape:. Its methods are inherent methods of that dyn type.
  2. trait.own.dyn-inherent.moduleIt must be declared in the module that declares the trait. Elsewhere in the trait's package it is an error. Error: nonlocal-impl.
  3. trait.own.dyn-inherent.packageIn another package it is an error. Error: orphan-impl.
  4. trait.own.dyn-inherent.receiverIts methods may be called only on a value whose type is that dyn type. A concrete type that implements the trait does not have them, and neither does a type parameter bounded by the trait. Error: unknown-method.
  5. trait.own.dyn-inherent.no-overrideThey are not members of the trait, so an implementation of the trait cannot declare or override them.
  6. trait.own.dyn-inherent.no-trait-implA trait implementation for a dyn type stays an error, as trait.target.trait-value.error states.

Why. Helpers that only make sense on an erased value, such as searching an error chain, belong to the dyn type, as in Rust's impl dyn Error. Keeping them out of the trait means no implementation can change what they do.

#Overlap

Implementations may be generic and state their bounds inline in the generic parameter list:

impl[T < Display] Printable for Box[T]:
    fn print(self) -> string:
        self.value.to_string()
  1. trait.overlap.genericImplementations may be generic and state their bounds inline in the generic parameter list.
  2. trait.overlap.constrained-headEvery type parameter of a generic implementation must be constrained. That means it appears in the implemented trait's arguments or in the target type, or an associated-type binding fixes it. A parameter that appears only in another bound is an error. One case is T in impl[T < Display, I < Holder[T]] Summary for Feed[I]. Error: unconstrained-impl-parameter.
  3. trait.overlap.constrained-bindingAn associated-type binding in the bound of a constrained parameter constrains the parameter it names, as T in impl[T < Display, I < Store[Item = T]] Summary for Feed[I].
  4. trait.overlap.head-projectionAn implementation's trait arguments and target type must not contain a projection of one of its type parameters, such as I::Item. Such a head is an error. Error: unconstrained-impl-parameter.
  5. trait.overlap.definitionTwo implementations overlap when they implement the same trait and their full heads unify.
  6. trait.overlap.unifyHeads unify when, after each implementation's parameters are renamed apart, one substitution makes both their trait arguments and their complete target types equal.
  7. trait.overlap.heads-onlyOverlap is decided from the implementation heads alone.
  8. trait.overlap.no-boundsBounds, including associated type bindings, are never used to claim that two implementations are disjoint.
  9. trait.overlap.numeric-familyAn implementation with a numeric-family target overlaps another implementation when one of the implementations it stands for does.
  10. trait.overlap.errorOverlapping implementations are an error. Error: overlapping-impl.
First implementation Second implementation Overlap
impl[T] Marker for List[T] impl Marker for List[i32] yes
impl[T] Marker for Box[T] impl Marker for Box[i32] yes
impl Marker for Box[i32] impl Marker for Box[string] no
impl Add[i32] for Money impl Add[Money] for Money no
impl[Args < Tuple, O, R] Marker for Fn[Args, O, $ R] impl Marker for fn(i32) -> i32 yes
impl Marker for fn(i32) -> i32 impl Marker for fn(string) -> i32 no
Error example
struct Box[T]:
    value: T

struct Plain:
    value: i32

trait Marker

impl[T < Display] Marker for Box[T]
impl Marker for Box[Plain]  # error: overlapping-impl
Error example
trait Holder[T]:
    fn get(self) -> T

trait Summary:
    fn summary(self) -> string

struct Feed[I]:
    items: I

impl[T < Display, I < Holder[T]] Summary for Feed[I]:  # error: unconstrained-impl-parameter
    fn summary(self) -> string:
        "items"
trait Store:
    type Item
    fn first(self) -> Self::Item

trait Summary:
    fn summary(self) -> string

struct Feed[I]:
    items: I

impl[T < Display, I < Store[Item = T]] Summary for Feed[I]:  # valid: the binding fixes T
    fn summary(self) -> string:
        "first: ${self.items.first()}"
Error example
trait Store:
    type Item
    fn first(self) -> Self::Item

trait Summary:
    fn summary(self) -> string

struct Feed[I]:
    items: I

impl[I < Store] Summary for Feed[I::Item]:  # error: unconstrained-impl-parameter
    fn summary(self) -> string:
        "items"

Why. Once I is known, its Item is known, so T has one value, as Rust's E0207 rule allows. A projection in a head runs the other way. Two types I may share one Item, so such a head can be neither matched nor checked for overlap.

Note. Because bounds are ignored, an implementation added later in a dependency cannot make two existing implementations overlap. A type parameter such as Args unifies with every tuple type, and a row parameter unifies with every row.

#Inherent Implementations

An inherent implementation, written impl T: without a trait, declares members attached directly to the nominal type T:

impl User:
    fn domain(self) -> string:
        self.email.slice(1, self.email.len())

#Inherent Members

  1. trait.inherent.declAn inherent implementation, written impl T: without a trait, declares members attached directly to the nominal type T rather than through a trait.
  2. trait.inherent.membersIts members are the type's inherent members, which the table below defines.
  3. trait.inherent.ownerOnly the package that owns T may declare them.
  4. trait.inherent.vs-traitAn inherent method differs from a trait method, which a trait declares and a trait implementation supplies for T.
Member Receiver Called
inherent method a receiver as its first parameter with dot syntax, as in user.domain()
inherent associated function none through the type, as in User::guest()
struct Counter:
    value: i32

impl Counter:
    fn read(self) -> i32: self.value
    fn zero() -> Counter: Counter { value: 0 }

See also: Implementation Ownership, Member Resolution.

#Method Visibility

  1. trait.inherent.privateInherent methods and inherent associated functions are module-private unless individually marked pub, including when their nominal type is public.
  2. trait.vis.trait-methodsMethods in trait declarations and trait implementations follow the trait's visibility.
  3. trait.vis.no-pubpub is not written on an individual trait method or its implementation.
  4. trait.vis.uniformA trait has one visibility level for all its methods; it cannot mix public and private methods.
struct Counter:
    value: i32

impl Counter:
    pub fn read(self) -> i32: self.value

#Inherent Member Names

  1. trait.inherent.unique-unifyingTwo inherent members with one name are an error when their implementations' targets unify, including two members of one implementation. Error: duplicate-inherent-member.
  2. trait.inherent.unique-unifying.disjointInherent implementations of one type constructor whose targets cannot unify may repeat a member name. impl Box[i32] and impl Box[string] may each declare show, but impl[T] Box[T] and impl Box[i32] must not.
  3. trait.inherent.unique-unifying.unifyTargets unify as implementation heads do in Overlap, after renaming each implementation's parameters apart.
  4. trait.inherent.target-matchAn inherent member is a member of a receiver's type only when its implementation's target matches that type. So box.show() on a Box[string] calls the member of impl Box[string].
  5. trait.inherent.field-namesAn inherent member may share its name with a field of the type, named or embedded, because fields and methods are separate namespaces.
  6. trait.inherent.no-overloadinghd-lang has no method or associated-function overloading.
Error example
struct User: pass

impl User:
    fn name(self) -> string: "first"
    fn name(self) -> string: "second"  # error: duplicate-inherent-member

struct Box[T]:
    value: T

impl[T] Box[T]:
    fn show(self) -> string: "box"

impl Box[i32]:
    fn show(self) -> string: "int box"  # error: duplicate-inherent-member

See also: Member Resolution.

#Inherent Associated Functions

Inherent associated functions are called through the nominal type:

impl User:
    fn guest() -> User:
        User { name: "guest" }

guest := User::guest()
  1. trait.inherent.assoc-callInherent associated functions are called through the nominal type.

#Method Resolution

This section defines which trait methods are usable at a use and how trait candidates are reported.

  1. trait.resolve.lookupFor a receiver of nominal type S, value.method(args) selects a method with the method lookup in Member Resolution.
  2. trait.resolve.lookup.kindsThat lookup covers inherent methods, trait methods, and promoted methods together.
  3. trait.resolve.no-fieldIt never selects a field.
struct Counter:
    value: i32

impl Counter:
    fn read(self) -> i32: self.value

fn demo(c: Counter) -> i32:
    c.read()

#Trait Availability

For a concrete receiver, a trait is available to dot-call lookup when its name is one of these:

Rule The trait's name is
trait.avail.moduleModule declared in the current module, or introduced by a use declaration in it
trait.avail.scopeScope visible in the current lexical scope
trait.avail.preludePrelude supplied by the prelude
  1. trait.avail.concreteFor a concrete receiver, a trait is available to dot-call lookup when its name is one of the cases in the table above.
  2. trait.avail.genericFor a generic receiver, its declared bounds are also available.
  3. trait.avail.dynamicA dynamic trait value always exposes the methods of its own erased trait.
  4. trait.avail.no-injectionAn implementation in the dependency graph does not inject its trait's method names into every module that can name the target type.
  5. trait.avail.not-candidateA method of a trait that is not available is not a candidate at all, wherever its implementation is declared.
  6. trait.avail.as-absentLookup proceeds as if the implementation were absent, so a promoted method of the same name may be selected.
  7. trait.avail.suggestA call that finds no method should suggest a use declaration for the trait.
use std.cmp.Eq

@derive(Eq)
struct Point:
    x: i32

fn same(a: Point, b: Point) -> bool:
    a == b   # Eq is available through the use

#Inherent Methods Win

  1. trait.resolve.inherent-firstA visible inherent method of the receiver's nominal type is always selected over trait methods. That includes a method of a trait that the same type implements.
trait Show:
    fn show(self) -> string

struct Wrapper:
    value: i32

impl Wrapper:
    fn show(self) -> string: "inherent"

impl Show for Wrapper:
    fn show(self) -> string: "trait"

fn demo(w: Wrapper) -> string:
    w.show()   # the inherent method wins

Why. Only the package that owns the type can declare an inherent method, so no other package can change which method such a call reaches.

#Ambiguous Methods

  1. trait.resolve.ambiguousSuppose more than one available trait that the receiver implements supplies a method with that name, and no inherent method of that name is usable. The call is then an error. Error: ambiguous-method.
  2. trait.resolve.ambiguous.defaultsThis holds whether each method is written in its implementation or comes from a default.
  3. trait.resolve.no-rankingThe compiler does not select by conversion ranking or declaration order.
  4. trait.resolve.qualified-fixA trait-qualified call resolves the ambiguity.
Error example
trait Left:
    fn label(self) -> i32
trait Right:
    fn label(self) -> i32
struct User:
    value: i32
impl Left for User:
    fn label(self) -> i32: self.value
impl Right for User:
    fn label(self) -> i32: self.value
fn main() -> i32: User { value: 42 }.label()  # error: ambiguous-method
Open in playground

See also: Member Resolution.

#Instantiations Of One Generic Trait

A receiver may implement one generic trait at several instantiations that each supply the method, as with impl Add[i32] for Money and impl Add[Money] for Money.

  1. trait.resolve.instantiationWhen the receiver implements one generic trait at several instantiations that each supply the method, the call chooses the instantiation.
  2. trait.resolve.instantiation.candidateEach instantiation is a candidate.
  3. trait.resolve.appliesA candidate applies when the call's arguments check against its method's parameter types, with that instantiation's trait arguments substituted.
  4. trait.resolve.applies.expectedWhen the call has an expected type, a candidate applies only if, in addition, the method's result type is assignable to it.
  5. trait.resolve.one-appliesExactly one applicable candidate is selected, so price.add(5) calls the Add[i32] method.
  6. trait.resolve.literal-argSuppose two or more candidates apply only because an unsuffixed literal argument could take several widths. The literal then has its default type, by types.literal.local.instantiation, and the candidate for that type is selected.
  7. trait.resolve.literal-arg.exampleWith impl Add[i32] for Money and impl Add[i64] for Money, price.add(-5) calls the Add[i32] method.
  8. trait.resolve.literal-arg.no-applyWith the same two implementations, price.add(5) is an error, because no candidate takes usize; write +5. Error: type-mismatch.
  9. trait.resolve.many-applyOtherwise two or more applicable candidates are an error, and a trait-qualified call such as Add::[i64]::add(price, 5) resolves it. Error: ambiguous-method.
  10. trait.resolve.no-applyWhen no candidate applies, the call is an error whose message lists the available instantiations. Error: type-mismatch.
  11. trait.resolve.one-trait-onlyThis choice applies only among instantiations of one trait. Methods of two different traits stay ambiguous whatever the argument types. Error: ambiguous-method.

Note. When the receiver is a literal, the call's arguments and implementations decide its width, by types.literal.local.form.receiver. A candidate here is then an instantiation of that one type.

Error example
trait Pick[T]:
    fn pick(self) -> T

struct Money:
    cents: i32

impl Pick[i32] for Money:
    fn pick(self) -> i32:
        self.cents

impl Pick[string] for Money:
    fn pick(self) -> string:
        "money"

fn invalid(price: Money) -> void:
    value := price.pick()  # error: ambiguous-method

#Trait-Qualified Calls

Select one trait explicitly with Trait::method(receiver, arguments...):

label := Display::to_string(value)
sum := Add::[Money]::add(left, right)
  1. trait.qualified.formTrait::method(receiver, arguments...) selects one trait explicitly.
  2. trait.qualified.receiverThe receiver is the first ordinary argument and must implement the named trait instantiation.
  3. trait.qualified.argumentsRemaining arguments follow normal positional/named ordering.
  4. trait.qualified.bypassThis form bypasses member lookup and selects exactly the named trait method.
  5. trait.qualified.type-argumentsA generic trait method takes its explicit type arguments after the method name, as in Identity::select::[i32](picker, 42).
  6. trait.qualified.trait-argumentsThe trait's own type arguments stay before ::.
  7. trait.qualified.generic-rulesThe type argument list follows the rules of Generic Functions.
  8. trait.qualified.declaringTrait::name, as a call or a reference, names only a member that Trait itself declares. Naming a supertrait's member is an error, and the message names the declaring trait. Error: unknown-method.
Error example
trait Parent:
    fn id(self) -> i32

trait Child < Parent:
    fn label(self) -> string

struct Item: pass

impl Parent for Item:
    fn id(self) -> i32: 1

impl Child for Item:
    fn label(self) -> string: "item"

fn show(item: Item) -> i32:
    first := Parent::id(item)  # valid: Parent declares id
    Child::id(item)            # error: unknown-method

Note. A dot call still reaches a supertrait's member, as in item.id(). The message says "declared by 'Parent'; write Parent::id".

#Associated Function Calls

An associated function is called through a type, a type parameter, or a trait:

trait Factory:
    fn create() -> Self

struct User:
    name: string

impl Factory for User:
    fn create() -> User:
        User { name: "new" }

fn make[T < Factory]() -> T:
    T::create()

first := User::create()
second := make::[User]()
  1. trait.assoc-call.typeType::f(args) first looks for an inherent associated function or method f of Type.
  2. trait.assoc-call.type.traitsWhen Type has no inherent member f, the candidates are the members named f of the available traits that Type implements.
  3. trait.assoc-call.type.ambiguousTwo or more trait candidates are an error, and a trait-qualified call resolves it. Error: ambiguous-method.
  4. trait.assoc-call.type.noneNo candidate at all is an error. Error: unknown-method.
  5. trait.assoc-call.parameterT::f(args), where T is a type parameter, resolves f through the bounds of T, and the call uses the bound's evidence.
  6. trait.assoc-call.parameter.oneExactly one trait among the bounds of T and their supertraits must declare f. Two or more are an error. Error: ambiguous-method.
  7. trait.assoc-call.parameter.staticT::f() is always resolved statically through a bound, never through a runtime type object.
  8. trait.assoc-call.traitTrait::f(args) for an associated function f infers Self like a generic argument of the call, from the arguments and the expected type.
  9. trait.assoc-call.trait.undeterminedA Trait::f(args) call whose Self that inference does not determine is an error; write Type::f(args) or T::f(args) instead. Error: cannot-infer-type.

See also: Trait-Qualified Calls, Trait Availability, and Templates, where a Structure:: call has the template's T as its Self.

#Generic Bounds And Static Dispatch

A generic bound requires explicit conformance and uses static dispatch:

fn show[T < Display](value: T) -> string:
    value.to_string()

Bounds compose with &:

fn audit[T < Display & Named](value: T) -> string:
    value.to_string() + " / " + value.name()
  1. trait.bound.staticA generic bound requires explicit conformance and uses static dispatch.
  2. trait.bound.compose-andBounds compose with &: the type must implement every trait joined by it.
  3. trait.bound.repeatA bound may list the same trait more than once, as in T < Display & Display. The repetition adds no requirement and is not diagnosed.
  4. trait.bound.supertraitsA bound implies a bound on each of its trait's transitive supertraits. Inside fn has_child[T < Child], a T value may be passed where T < Parent is required.
  5. trait.bound.unsatisfiedA type argument, explicit or inferred, that does not implement a trait its parameter's bound requires is an error. Error: unsatisfied-trait-bound.
  6. trait.bound.unsatisfied.casesThis includes a non-reference type for T < AnyRef and a reference type for T < AnyVal.
  7. trait.bound.depthA bound required directly by a use has depth 1. A bound of the implementation that proves a bound of depth n has depth n + 1.
  8. trait.bound.depth.limitA proof that needs a bound of depth greater than 64 is an error, whether or not a deeper proof would succeed. Error: trait-resolution-depth.
  9. trait.bound.depth.fixedThe limit is fixed by this specification; no package, module, or compiler option changes it.
  10. trait.bound.representationThe compiler may monomorphize static calls, share one body among instantiations, or use another representation.
  11. trait.bound.representation.semanticsThe chosen representation must preserve the observable semantics.
trait Parent:
    fn parent(self) -> i32

trait Child < Parent:
    fn child(self) -> i32

fn has_parent[T < Parent](value: T) -> i32:
    value.parent()

fn has_child[T < Child](value: T) -> i32:
    has_parent(value) + value.child()

Why. A fixed limit makes every implementation accept the same programs, and it ends bound solving that would otherwise never finish.

See also: the non-normative Implementation Model.

#Declared Bounds Are Not Implied

A generic type's declared bounds are never implied where the type is written. Map is declared as Map[K < Eq & Hash, V], so each declaration that writes Map[K, V] bounds its own K:

fn stock_of[K < Eq & Hash, V](stock: Map[K, V], item: K) -> V?:
    stock.get(item)

struct Index[K < Eq & Hash, V]:
    name: string
    entries: Map[K, V]

impl[K < Eq & Hash, V] Index[K, V]:
    fn lookup(self, key: K) -> V?:
        self.entries.get(key)
  1. trait.bound.no-impliedA generic type's declared bounds are never implied where the type is written.
  2. trait.bound.no-implied.positionsThis holds wherever a type is written, including an impl header's target and trait arguments, a function or method signature, a field, and a bound.
  3. trait.bound.no-implied.enclosingA declaration whose generic parameter T appears in a written C[T] must itself give T every bound that C's parameter requires. Error: unsatisfied-trait-bound.

So impl[K, V] Iterable[(K, V)] for Map[K, V] and fn count[K, V](m: Map[K, V]) -> usize are errors. Writing the bound fixes each, as in impl[K < Eq & Hash, V] Iterable[(K, V)] for Map[K, V].

Error example
trait Catalog:
    fn catalog_name(self) -> string

impl[K, V] Catalog for Map[K, V]:  # error: unsatisfied-trait-bound
    fn catalog_name(self) -> string:
        "inventory"

fn stock_report[K, V](stock: Map[K, V], title: string) -> string:  # error: unsatisfied-trait-bound
    "Stock report: " + title

struct Index[K, V]:
    name: string
    entries: Map[K, V]  # error: unsatisfied-trait-bound

Note. The bounds a declaration writes are ordinary bounds, so its bodies may use them, as lookup calls get above. There is no separate implied-bound mechanism.

Why. Every requirement is visible in the declaration that relies on it, as in Rust. A bound written once at the type would otherwise leak silently into every impl and signature that names the type.

See also: trait.bound.unsatisfied, trait.avail.generic, and types.map-key.declared-bound.

#Associated Type Bindings

A trait in a generic parameter bound may bind associated types after its positional type arguments:

trait Supplier:
    type Item
    fn get(self) -> Self::Item

fn describe[T < Display, I < Supplier[Item = T]](source: I) -> string:
    source.get().to_string()

struct Feed[I]:
    source: I

impl[T < Display, I < Supplier[Item = T]] Display for Feed[I]:
    fn to_string(self) -> string:
        describe(self.source)
  1. trait.binding.formA trait in a generic parameter bound may bind associated types after its positional type arguments.
  2. trait.binding.meaningI < Supplier[Item = T] means that I implements Supplier and that its associated type Item equals T.
  3. trait.binding.equalityThe binding is an equality constraint on the projection I::Item, not a new type.
  4. trait.binding.insideInside the declaration, I::Item remains a valid projection and denotes the same type as T.
  5. trait.binding.interchangeableThe two spellings are interchangeable in parameter, result, and body types.
  6. trait.binding.use-siteAt a use site, after substitution, the argument's implementation of the trait must bind the associated type to the bound type.
  7. trait.binding.inferenceThe constraint participates in generic argument inference, so T above is inferred from the Supplier implementation of the argument passed for I.
  8. trait.binding.mismatchAn argument whose implementation binds a different type is an error. Error: unsatisfied-trait-bound.
  9. trait.binding.reachableA binding counts as a bound reachable from its parameter.
  10. trait.binding.reachable.exampleIn the implementation above, T is therefore constrained through I. The implementation satisfies the rule that every generic implementation parameter be constrained.

#Binding Names

A binding may name an associated type that a supertrait declares:

trait Supplier:
    type Item
    fn get(self) -> Self::Item

trait NamedSupplier < Supplier:
    fn name(self) -> string

fn first[T, I < NamedSupplier[Item = T]](source: I) -> T:
    source.get()
  1. trait.binding.scopeThe bound type may name any parameter of the same generic parameter list.
  2. trait.binding.name-reachA binding name must be an associated type that the named trait declares or reaches through its supertraits, as NamedSupplier reaches Supplier's Item. Naming anything else is an error. Error: unknown-associated-type.
  3. trait.binding.name-reach.meaningA binding of a supertrait's associated type constrains that supertrait's projection, so I::Item above equals T.
  4. trait.binding.name-reach.ambiguous-typeWhen two different associated type declarations reachable that way have the name, the binding is ambiguous. An ambiguous binding is an error. Error: ambiguous-associated-type.
  5. trait.binding.onceEach projection may be bound at most once in one generic parameter list.
  6. trait.binding.once.errorA second binding of the same parameter's associated type, in the same bound or another bound, is an error. This holds even when both bindings name the same type. Error: duplicate-associated-binding.
  7. trait.binding.once.elaboratedThese two rules count written bindings only. A binding that arrives through a supertrait follows trait.binding.super.merge instead.
Error example
trait Supplier:
    type Item
    fn get(self) -> Self::Item

fn first[T, I < Supplier[Element = T]](source: I) -> T:  # error: unknown-associated-type
    source.get()

fn second[T, I < Supplier[Item = T, Item = T]](source: I) -> T:  # error: duplicate-associated-binding
    source.get()

trait Named:
    type Item

trait Keyed:
    type Item

trait Record < Named & Keyed

fn third[T, R < Record[Item = T]](record: R) -> T:  # error: ambiguous-associated-type
    panic("unreachable")

Why. As in Rust, a subtrait's bound can fix a supertrait's associated type without a second bound. Only a name that two supertraits declare separately needs the longer spelling.

#Binding Positions

  1. trait.binding.positions-keyBindings appear in generic parameter bounds, in supertrait lists, in trait value types, and in requirement keys.
  2. trait.binding.rejected-trait-typeThe trait of an impl header, a trait-qualified call, and a method reference do not accept them. The grammar reports an error there. Error: syntax-error.
  3. trait.binding.non-traitA binding in the arguments of a named type that is not a trait, as in List[i32, Item = i32], is an error. Error: unknown-associated-type.
  4. trait.binding.ambiguous-projectionA binding does not make an ambiguous projection unambiguous. When two bounds on I both declare Item, I::Item is still ambiguous even if one of them binds it. Error: ambiguous-associated-type.
trait Source:
    type Item
    fn get(self) -> Self::Item

fn demo[S < Source[Item = i32]](s: S) -> i32:
    s.get()

#Supertrait Bindings

A supertrait may bind an associated type of its trait, so every implementation fixes it:

use std.ops.Add

trait Summable < Add[Out = Self]

fn double[T < Summable](value: T) -> T:
    value + value
  1. trait.binding.super.formA trait in a supertrait list may bind associated types after its positional arguments, as a bound does.
  2. trait.binding.super.meaningtrait C < S[A, Out = U] requires that a type X implementing C implement S[A] with Out equal to U, where A and U read X for Self.
  3. trait.binding.super.projectionFor a type parameter T < C, and for Self inside C, the projection is known to equal U. So value + value above has type T.
  4. trait.binding.super.mismatchAn impl C for X whose implementation of S[A] binds Out to another type is an error. Error: missing-supertrait-implementation.
  5. trait.binding.super.namesThe rules of Binding Names apply to a supertrait list as to a generic parameter list.
  6. trait.binding.super.mergeWhen two supertrait paths of a trait bind the same associated type to the same type, the bindings merge into one.
  7. trait.binding.super.conflictWhen the two paths bind it to different types, the trait declaration is an error. The diagnostic is reported at the declaring trait and names both paths. Error: duplicate-associated-binding.
  8. trait.binding.super.conflict.boundThe bounds of one generic parameter follow the same two rules, as in T < Numbers & Words. A conflict there is reported on that parameter's bound list. A binding written in one bound that names a different type from the one the bound's own supertraits fix is the same conflict, as in T < Prices[Item = string] under trait Prices < Source[Item = i32]. Error: duplicate-associated-binding.
Error example
trait Source:
    type Item
    fn get(self) -> Self::Item

trait Counts < Source[Item = i32]:
    fn count(self) -> i32

trait Sums < Source[Item = i32]:
    fn sum(self) -> i32

trait Stats < Counts & Sums  # valid: both paths bind Item = i32

trait Numbers < Source[Item = i32]

trait Words < Source[Item = string]

trait Both < Numbers & Words  # error: duplicate-associated-binding

Why. Equal bindings say the same thing twice, as a diamond often does. Different ones would make i32 and string equal inside every body that names the trait.

Error example
use std.ops.Add

trait Summable < Add[Out = Self]

struct Money:
    cents: i64

impl Add for Money:
    type Out = i64
    fn add(self, rhs: Money) -> i64:
        self.cents + rhs.cents

impl Summable for Money  # error: missing-supertrait-implementation

#Dynamic Trait Values

A trait value is a shared or mutable reference to an object with dynamic dispatch, written &dyn Tr or &mut dyn Tr:

fn print_display(value: &dyn Display) -> void $ Console:
    println(value.to_string())
  1. trait.dyn.reference-formA trait value type is written &dyn Tr, &mut dyn Tr, *dyn Tr, or *mut dyn Tr, for a trait Tr.
  2. trait.dyn.reference-contentsA trait value reference refers to a concrete object and carries dispatch metadata for the trait.
  3. trait.dyn.reference-keywordA bare trait name in a type position is an error: its fix-it inserts dyn after an existing & or &mut, and writes &dyn otherwise. Error: trait-used-as-type.
  4. trait.dyn.bare-value-rejectedA written dyn Tr is invalid as a value type, including within a container or alias. Error: trait-used-as-type.
  5. trait.dyn.bare-value-no-fixThe bare trait value error has no fix-it.
  6. trait.dyn.referent-descriptorThe descriptor dyn Tr is valid as the immediate referent of &, &mut, *, or *mut, optionally grouped, or as an inherent implementation target.
  7. trait.dyn.reference-no-bare-readExplicitly dereferencing a trait value reference would produce an unsized bare value and is an error. Error: trait-used-as-type.
  8. trait.dyn.keyword.positionsThe type positions include parameter, result, field, payload, and local annotation types, type arguments, and associated type bindings.
  9. trait.dyn.keyword.bareA trait stays bare where a trait, not a type, is named: a bound, a supertrait list, an implementation header's trait, a requirement key, and a trait-qualified call.
  10. trait.dyn.value-methodsThe available methods of the trait and of its transitive supertraits, and the methods of an inherent impl dyn block, may be called through the value. The concrete type's inherent methods may not.
Error example
trait Greet:
    fn greet(self) -> string

fn demo(g: Greet) -> string:  # error: trait-used-as-type
    g.greet()

Why. &dyn makes the reference and runtime dispatch explicit. A bound such as T < Greet stays fully monomorphized.

#Dynamic Safety

  1. trait.dyn.any-traitEvery trait may be used as a dyn type. No rule about a trait's members makes its dyn type invalid.
  2. trait.dyn.member.unavailableA member of the trait or of a supertrait that cannot work dynamically is unavailable on the dyn value.
  3. trait.dyn.member.unavailable.errorCalling an unavailable member through a dyn value is an error, reported at the call. Error: dyn-member-unavailable.

Each kind of member is available or unavailable on a dyn value:

Rule Form On a dyn value
trait.dyn.member.assoc-functionAssociated functions an associated function, which has no self receiver unavailable
trait.dyn.member.selfSelf a method that names Self anywhere other than its receiver unavailable
trait.dyn.member.opaque-resultOpaque results a method whose return annotation contains impl Trait unavailable
trait.dyn.safe.assoc-type.reference-valuesAssociated types a method whose signature names an associated type available; the dyn type binds every associated type, as &dyn Supplier[Item = i32] does
trait.dyn.safe.method-type-paramMethod type parameters a method-level type parameter, whatever its bounds, as in T < Display or T < Error available; a call through the trait value may pass any type argument, a value type included
trait.dyn.safe.suspendingSuspending methods a suspending method, as the prelude Console's write! is available
trait.dyn.safe.row-parameterRow parameters a method-level row parameter available
  1. trait.dyn.member.other-with-opaqueEvery method outside the unavailable categories above is available.
  2. trait.dyn.member.type-param-useA method type parameter may be used in any way in the method's signature and body, including inside a container or a function type.
  3. trait.dyn.generic-traitGeneric parameters of the trait itself are allowed when the value type names one complete instantiation.
  4. trait.dyn.generic-trait.self-defaultA trait value type that omits a parameter whose default names Self is an error, since no Self is known there. Error: partial-generic-arguments.
  5. trait.dyn.opaque-result.no-slotAn opaque-result method has no dynamic dispatch table slot.
  6. trait.dyn.opaque-result.hintA diagnostic for calling an opaque-result method through a trait value reference suggests using a generic bound.
trait Runner:
    fn run[$R](self, job: fn() -> void $ R) -> void $ R

fn valid(runner: &dyn Runner) -> void:
    pass

A method type parameter bounded by Error, whose argument may be an enum, keeps the method available:

use std.error.Error

trait Registry:
    fn lookup[T < Error](self, name: string) -> T?

fn valid(registry: &dyn Registry) -> void:
    pass

A method that takes Self is unavailable, while the rest of the trait works:

Error example
trait Versioned:
    fn version(self) -> i32
    fn newer_than(self, other: Self) -> bool

fn audit(record: &dyn Versioned) -> bool:
    if record.version() > 0:
        return true
    record.newer_than(record)  # error: dyn-member-unavailable

trait Source:
    fn items(&self) -> impl Iterator[i32]

fn unavailable(source: &dyn Source) -> void:
    source.items()  # error: dyn-member-unavailable

Why. A member is judged where it is called, as in Swift 5.7, so one unusual member never blocks a whole trait. An associated function has no receiver to dispatch on. A Self parameter needs the concrete type, which the dyn value has erased. A bound associated type makes every signature that uses it concrete.

See also: Trait Values And Any.

#Bound Associated Types

A trait value type binds the trait's associated types as a bound does:

trait Supplier:
    type Item
    fn get(self) -> Self::Item

struct Constant:
    value: i32

impl Supplier for Constant:
    type Item = i32
    fn get(self) -> i32: self.value

fn read(source: &dyn Supplier[Item = i32]) -> i32:
    source.get() + 1

fn first[T < Supplier](source: T) -> T::Item:
    source.get()

fn run() -> i32:
    let source: &dyn Supplier[Item = i32] = &Constant { value: 41 }
    first(source) + read(source)
  1. trait.dyn.binding.form.reference-valuesA trait value type may bind associated types after its positional arguments, as in &dyn Supplier[Item = i32].
  2. trait.dyn.binding.complete.reference-valuesIt must bind every associated type of the trait and of its supertraits. A binding that a supertrait list fixes counts as bound, so under trait Sub < Super[Item = T] the types &dyn Sub and &dyn Sub[Item = T] are one type. A trait value type that leaves one unbound is an error. Error: trait-not-dynamically-safe.
  3. trait.dyn.binding.self-fixedA binding to Self that a supertrait list fixes, as in trait Summable < Add[Out = Self], counts as bound. The trait value does not carry it, so Out stays a projection.
  4. trait.dyn.binding.namesThe rules of Binding Names apply, so a binding may name a supertrait's associated type. An unknown name is an error. Error: unknown-associated-type. An ambiguous name is an error. Error: ambiguous-associated-type.
  5. trait.dyn.binding.signaturesThrough the value, each projection in a method signature denotes its bound type, so get above returns i32.
  6. trait.dyn.binding.reference-convertA concrete reference converts to a trait value reference only when its referent's implementation binds each associated type to the bound type. Any other such conversion is an error. Error: type-mismatch.
  7. trait.dyn.binding.identityTwo trait value types are the same type when they name the same trait instantiation and bind each associated type to the same type. Order does not matter.
  8. trait.dyn.binding.widen.reference-valuesWidening to a supertrait value keeps the bindings of the associated types that the supertrait reaches, so &dyn NamedSupplier[Item = i32] widens to &dyn Supplier[Item = i32].
Error example
trait Supplier:
    type Item
    fn get(self) -> Self::Item

trait Factory:
    type Item
    fn count() -> i32

fn unbound(source: &dyn Supplier) -> void:  # error: trait-not-dynamically-safe
    pass

fn total[F < Factory[Item = i32]]() -> i32:
    F::count()

fn with_function(factory: &dyn Factory[Item = i32]) -> i32:
    total::[*dyn Factory[Item = i32]]()  # error: unsatisfied-trait-bound

Why. Rust accepts &dyn Iterator<Item = T>, and hd had simply not added the form. Once every associated type is bound, every method signature is concrete. An associated function still has no receiver to dispatch on, so it stays unavailable whatever the type binds.

#Supertrait Widening

  1. trait.dyn.supertrait-methodsA child-trait bound or dynamic value exposes the methods of its transitive supertraits.
  2. trait.dyn.widenA dynamic child-trait value widens implicitly to a supertrait value, losing access to child-only methods.
  3. trait.dyn.no-narrowNo conversion reverses the widening, so converting back is an error. Error: type-mismatch.
  4. trait.dyn.recoverOnly a value of Inspectable or of a trait that extends it can recover its concrete type, through Runtime Type Identity.
trait Animal:
    fn name(self) -> string

trait Dog < Animal:
    fn bark(self) -> string

struct Pup:
    name: string

impl Animal for Pup:
    fn name(self) -> string: self.name

impl Dog for Pup:
    fn bark(self) -> string: "woof"

fn name_of(animal: &dyn Animal) -> string:
    animal.name()

fn demo(pup: Pup) -> string:
    name_of(&pup)   # the reference converts to Animal

#Trait Values As Bounds

trait Named < Display:
    fn name(self) -> string

fn show[T < Display](value: T) -> string:
    value.to_string()

fn tag[T < Named](value: T) -> string:
    value.name()

fn describe(named: &dyn Named, shown: &dyn Display) -> string:
    tag(named) + show(named) + show(shown)
  1. trait.dyn.bound.referenceA trait value reference satisfies bounds on its trait and supertraits when every required member is available through that reference.
  2. trait.dyn.bound.reference-sharedA shared reference provides self and &self members; a &mut self member requires a mutable trait value reference.
  3. trait.dyn.bound.reference-projectionProjections through such a bound use the associated types bound by the referenced trait value.
  4. trait.dyn.bound.reference-bindingA bound's associated-type bindings and trait arguments must match the referenced trait value's bindings and arguments. Error: unsatisfied-trait-bound.
  5. trait.dyn.bound.reference-dispatchCalls through such a bound dispatch through the referenced trait value's table.
  6. trait.dyn.bound.reference-no-implThis adds no implementation and permits no trait value reference as an implementation target.

Why. Generic code under T < Tr may call any member of Tr, including an associated function such as T::create(). The availability condition, with the bound associated types, keeps every such call meaningful for a dyn argument.

#Conversion To Trait Values

  1. trait.dyn.convert.reference-valuesConverting a concrete reference to a trait value reference requires an implementation of the trait for its referent type. A conversion without one is an error. Error: type-mismatch.
  2. trait.dyn.convert.any-typeThe concrete type can be composite or primitive.
  3. trait.dyn.type-testA dynamic trait value supports a type test only when its trait is Inspectable or extends it. The test recovers an exact concrete type.
  4. trait.dyn.no-trait-testNo test asks whether a value implements another trait.
trait Greet:
    fn greet(self) -> string

struct User:
    name: string

impl Greet for User:
    fn greet(self) -> string: "hi"

fn demo(user: User) -> *dyn Greet:
    &user   # the reference converts through the explicit implementation

See also: Runtime Type Identity.

#Trait Value References

A trait value reference, written &dyn Tr or &mut dyn Tr, is a trait value that is a reference: it refers to one object whose type implements Tr. It is made only from a reference the program writes or already holds, as in Rust: &s, &mut s, &S { ... }, or a &S value. Bare dyn Tr is not a value type.

trait Counter:
    fn bump(&mut self) -> void
    fn total(&self) -> i32

struct Tally:
    n: i32

impl Counter for Tally:
    fn bump(&mut self) -> void:
        self.n += 1
    fn total(&self) -> i32:
        self.n

fn demo() -> i32:
    let mut tally = Tally { n: 0 }
    let shared: &mut dyn Counter = &mut tally
    shared.bump()                              # changes `tally`
    let fresh: &dyn Counter = &Tally { n: 5 }  # a new object
    tally.bump()
    tally.n + fresh.total()                    # 2 + 5
  1. trait.dyn.ref.form&dyn Tr and &mut dyn Tr are trait value references. They are reference types, as Reference Types defines.
  2. trait.dyn.ref.share&S converts implicitly to &dyn Tr, and &mut S to &mut dyn Tr, when S implements Tr. The result refers to the place the source refers to.
  3. trait.dyn.ref.writtenA value that is no reference never converts to &dyn Tr or &mut dyn Tr. Using one where a trait value reference is expected is an error, so a new object is written &e or &mut e, as in &S { ... }. Error: type-mismatch.
  4. trait.dyn.ref.written.reference-fix.reference-valuesThe error's fix-it writes & before the value, or &mut where &mut dyn Tr is expected, for a place or struct literal whose type implements Tr. No fix-it offers &mut on a readonly view.
  5. trait.dyn.ref.convert.implEither conversion is an error when S does not implement Tr explicitly. Error: type-mismatch.
  6. trait.dyn.ref.weaken&mut dyn Tr converts implicitly to &dyn Tr, and both refer to the same object.
  7. trait.dyn.ref.no-upgrade&dyn Tr and &S never convert to &mut dyn Tr. Error: mutable-upgrade.
  8. trait.dyn.ref.widenA child trait value reference converts implicitly to a supertrait value reference of the same mutability, which refers to the same object.
  9. trait.dyn.ref.memberThrough &dyn Tr, the methods declared self and &self are available. Through &mut dyn Tr, the methods declared mut self and &mut self are available too.
  10. trait.dyn.ref.member.mutCalling a mut self or &mut self method through &dyn Tr is an error. Its fix-it writes &mut dyn in the declared type of the receiver binding. Error: not-writable.
  11. trait.dyn.ref.member.other.reference-valuesOther members follow Dynamic Safety.
  12. trait.dyn.ref.write-throughA method that changes its receiver, called through a trait value reference, changes the object the reference refers to. Through a reference converted from &mut s, that object is s itself.
  13. trait.dyn.ref.identityis on two trait value references compares the places they refer to, by ref.identity.same-place. Two conversions of one &s refer to one place, and two conversions of &e on a value refer to two new objects.
  14. trait.dyn.ref.no-store*r = v and *r op= v with a trait value reference r are errors, since r does not know the type of what it refers to. Error: type-mismatch.
  15. trait.dyn.ref.counted*dyn Tr and *mut dyn Tr are counted trait value references. Each rule of this section holds for them with * in place of & on both sides of a conversion, so *mut S converts to *mut dyn Tr.
  16. trait.dyn.ref.counted.lendA counted trait value reference lends itself as a local one, as ref.convert.lend states: *dyn Tr converts to &dyn Tr.
  17. trait.dyn.ref.counted.memberThrough *dyn Tr, the methods declared *self are available too, and through *mut dyn Tr also those declared *mut self.
Error example
trait Counter:
    fn bump(&mut self) -> void

fn poke(counter: &dyn Counter) -> void:
    counter.bump()  # error: not-writable

fn upgrade(counter: *dyn Counter) -> *mut dyn Counter:
    counter  # error: mutable-upgrade

struct Tally:
    n: i32

impl Counter for Tally:
    fn bump(&mut self) -> void:
        self.n += 1

fn count(counter: &mut dyn Counter) -> void:
    counter.bump()

fn demo() -> i32:
    let mut tally = Tally { n: 0 }
    count(tally)  # error: type-mismatch; the fix-it writes `count(&mut tally)`
    tally.n

Why. A trait value reference is how a callee changes what the caller holds. A conversion that made a new object for a value would drop the callee's writes, so the program writes the & that says which object it means, as Rust requires. Writing &mut says that methods called through it may change the object. A method call's receiver still takes its address, by fn.method.auto-address.

See also: Values And References, Reference Receivers.

#Any

Any is the universal empty trait.

  1. trait.any.universalAny is the universal empty trait.
  2. trait.any.allEvery value type, including an optional type, implements Any automatically.
  3. trait.any.nevernever implements Any as well, by types.any.never.
  4. trait.any.erases.reference-valuesThe reference type &dyn Any hides its referent's concrete type and exposes no type-specific methods.
  5. trait.any.optional.reference-valuesA reference to an optional value erases to &dyn Any like a reference to any other enum value.
  6. trait.any.none.reference-valuesA bare .None still needs an expected optional type: let value: &dyn Any = &.None is an error, while let value: (&dyn Any)? = .None is valid. Error: missing-contextual-enum-type.
Error example
let invalid: *dyn Any = &.None  # error: missing-contextual-enum-type

#Sealed Traits

A sealed trait is a standard trait whose implementations only the compiler and the standard library supply.

Rule Trait Implemented for
trait.sealed.anyAny Any every value type (Any)
trait.sealed.anyval-membersAnyVal AnyVal the primitive types, string, tuples (void included), enums (optionals and Result included), structs, List, Map, function types, and newtypes over them (Trait Values And Any)
trait.sealed.anyref-membersAnyRef AnyRef the reference types, trait value references included, and the types whose values are shared objects, which lists and maps are not (Trait Values And Any)
trait.sealed.inspectableInspectable Inspectable the inspectable types (Inspectable Types)
trait.sealed.structureStructure Structure each derivation's target, while its template is instantiated (The Structure Trait)
trait.sealed.numNum Num every integer and floating-point type (Numeric Traits)
trait.sealed.integerInteger Integer i8, i16, i32, i64, u8, u16, u32, u64, and usize (Numeric Traits)
trait.sealed.floatFloat Float f32 and f64 (Numeric Traits)
trait.sealed.tupleTuple Tuple every tuple type (Function Type Constructors)
  1. trait.sealed.definitionA sealed trait is a standard trait whose implementations only the compiler and the standard library supply.
  2. trait.sealed.listThe sealed traits are those in the table above.
  3. trait.sealed.use-positions-structureUser code may name a sealed trait in a bound, as a supertrait, and, where the trait is dynamically safe, as a value type. Structure is the exception: it may be named only where annot.structure.named-positions allows.
  4. trait.sealed.no-user-implUser code cannot implement a sealed trait.
  5. trait.sealed.user-implAn impl of a sealed trait outside the standard library is an error, reported on the impl line, whatever its target. Error: sealed-trait-implementation.
Error example
use std.inspect.{Inspectable, TypeId}

struct User:
    name: string

struct Handle: pass

impl Inspectable for User:  # error: sealed-trait-implementation
    fn runtime_type(self) -> TypeId: TypeId::of::[string]()

impl AnyRef for Handle  # error: sealed-trait-implementation

#Compiler-Supplied Implementations

  1. trait.sealed.suppliedThe compiler supplies the implementations listed for each sealed trait.
  2. trait.sealed.behavesA compiler-supplied implementation behaves like an explicit one: it satisfies bounds, constructs dynamic trait values, and counts for supertrait checks.
  3. trait.sealed.no-replaceA compiler-supplied implementation cannot be replaced or overridden.
  4. trait.sealed.member-namesA trait may have a sealed trait as a direct or transitive supertrait. Such a trait must not declare a member with the name of one of that sealed trait's members.
  5. trait.sealed.member-names.implAn implementation of such a trait must not write such a member either.
  6. trait.sealed.member-names.errorEither is an error, reported on the member, in place of duplicate-trait-member. Error: sealed-trait-implementation.
  7. trait.sealed.inherent-okAn inherent method with the same name is allowed.
  8. trait.sealed.inherent-lookupOrdinary method lookup finds that inherent method on the concrete type, and it changes nothing that the compiler-supplied implementation reports.
use std.inspect.Inspectable

struct User:
    name: string

fn id_of(value: User) -> string:
    "${value.runtime_type()}"   # no impl Inspectable for User written: the compiler supplies it

#Extending A Sealed Trait

  1. trait.sealed.extendA trait that extends a sealed trait is declared and implemented normally.
  2. trait.sealed.extend.membersIts implementation writes only the child's members; the sealed supertrait's implementation comes from the compiler.
  3. trait.sealed.extend.missingWhen the target is not a type the compiler supplies the sealed trait for, the implementation is an error, as for any missing supertrait. Error: missing-supertrait-implementation.
use std.inspect.Inspectable

trait Named < Inspectable:
    fn type_name(self) -> string

struct User:
    name: string

impl Named for User:
    fn type_name(self) -> string: "User"

#Numeric Traits

std.num declares three sealed traits for the primitive number types, so generic code can compute over any of them:

use std.num.Num

fn sum[T < Num](items: List[T]) -> T:
    let total = T::zero()
    for item in items:
        total = total + item
    total

fn kilo[N < Num](n: N) -> N:
    n * N::from_i64(1000)

Num is declared in this shape:

pub trait Num < AnyVal & PartialOrd & Display & Add[Out = Self] & Sub[Out = Self] & Mul[Out = Self] & Div[Out = Self] & Rem[Out = Self]:
    fn zero() -> Self
    fn one() -> Self
    fn from_i64(n: i64) -> Self
Rule Trait Supertraits
trait.num.num-orderedNum Num AnyVal, PartialOrd (and so Eq), Display, and Add, Sub, Mul, Div, and Rem, each as [Out = Self]
trait.num.integerInteger Integer Num, Ord, BitAnd, BitOr, and BitXor, each as [Out = Self], Not[Out = Self], Shl[u32, Out = Self], and Shr[u32, Out = Self]
trait.num.floatFloat Float Num, PartialOrd, and Neg[Out = Self]
  1. trait.num.modulestd.num declares Num, Integer, and Float, with the supertraits in the table above.
  2. trait.num.sealedThe three traits are sealed. They stand for the built-in primitive number types only, and only the standard library implements them.
  3. trait.num.not-newtypesA newtype over a number, such as type Meters(i64), and a library number type, such as a BigInt, are not Num. They implement the operator traits they need by hand.
  4. trait.num.members.stdNum declares zero, one, and from_i64. The other numeric methods, such as checked_add and is_nan, are stdlib-tier inherent methods of the number types.
  5. trait.num.zero-oneT::zero() and T::one() are the values 0 and 1 of T. A numeric literal never has a type parameter's type, so generic code builds constants from these functions.
  6. trait.num.from-i64-checkedT::from_i64(n) is checked in every build. For an integer type T, it returns n as a T when T can hold it, and otherwise panics with integer-overflow. A floating-point type takes the nearest value.
  7. trait.num.from-i64-castA numeric cast T(n) stays the way to wrap.
  8. trait.num.division/ and % keep each type's own meaning under Num. Integer division truncates and panics on a zero divisor, and floating-point division follows IEEE 754.
  9. trait.num.std-operatorsThe standard library's operator implementations behave as the built-in operators do.
Error example
use std.num.Num

type Meters(i64)

fn sum[T < Num](items: List[T]) -> T:
    let total = T::zero()
    for item in items:
        total = total + item
    total

fn total(items: List[Meters]) -> Meters:
    sum(items)  # error: unsatisfied-trait-bound

A library number type implements the operator and comparison traits directly, never Num:

Error example
use std.num.Num
use std.ops.Add

struct Cents:
    value: i64

impl Add for Cents:
    type Out = Cents
    fn add(self, rhs: Cents) -> Cents:
        Cents { value: self.value + rhs.value }

impl Eq for Cents:
    fn eq(self, other: Cents) -> bool:
        self.value == other.value

impl Num for Cents:  # error: sealed-trait-implementation
    fn zero() -> Cents: Cents { value: 0 }
    fn one() -> Cents: Cents { value: 1 }
    fn from_i64(n: i64) -> Cents: Cents { value: n }

Note. The stdlib chapter Num specifies those methods, such as checked_add on every integer type and is_nan on f64.

Why. Sealing keeps the families to types whose operators the compiler knows. zero and one replace polymorphic literals, the simpler model of Rust's num-traits rather than Haskell's fromInteger.

See also: Operator Traits, Supertrait Bindings, and the stdlib chapter Num.

#Runtime Type Identity

The standard module std.inspect lets a program erase a value so that its concrete type can be recovered later:

trait Inspectable:
    fn runtime_type(self) -> TypeId

impl dyn Inspectable:
    pub fn downcast_ref[T < Inspectable](&self) -> (*T)?
    pub fn downcast_mut[T < Inspectable](&mut self) -> (*mut T)?

impl TypeId:
    pub fn of[T < Inspectable]() -> TypeId
  1. trait.rtti.moduleThe standard module std.inspect lets a program erase a value so that its concrete type can be recovered later.
  2. trait.rtti.reference-importsIts names are not prelude names; code imports them, as in use std.inspect.{Inspectable, TypeId}.
  3. trait.rtti.reference-surfaceThe block above lists the public surface.
  4. trait.rtti.reference-bodiesThe compiler supplies the bodies of downcast_ref and downcast_mut.

#Inspectable And TypeId

  1. trait.inspect.sealedInspectable is a sealed trait.
  2. trait.inspect.reference-safeInspectable is dynamically safe; its only trait member is runtime_type.
  3. trait.inspect.suppliedThe compiler supplies its implementation for every inspectable type.
  4. trait.inspect.supplied.reference-methodsThe implementation provides runtime_type; inherent recovery methods add no vtable requirement.
  5. trait.inspect.runtime-typevalue.runtime_type() returns the TypeId of the value's recorded type.
  6. trait.inspect.staticFor a value whose static type is concrete, the recorded type is its static type.
  7. trait.inspect.dynamicFor a dynamic value of Inspectable, it is the concrete type recorded when the value was erased, never the trait. The same holds for a dynamic value of a trait that has Inspectable as a supertrait.
  8. trait.inspectable.referenceA reference type is inspectable when its referent is inspectable or is a trait value type.
  9. trait.inspect.reference-recordedFor a trait value reference, runtime_type() returns the runtime identity of the concrete referent whose reference was erased.
use std.inspect.Inspectable

fn id_of[T < Inspectable](value: T) -> string:
    "${value.runtime_type()}"

See also: Trait Values And Any.

#TypeId Values

  1. trait.typeid.opaque-structTypeId is an opaque struct, a value that copies as any struct does. User code cannot construct one or read its fields.
  2. trait.typeid.traitsTypeId implements Eq, Hash, and Display.
  3. trait.typeid.ofTypeId has the associated function TypeId::of::[T](), which returns the TypeId of T.
  4. trait.typeid.equalityEquality holds exactly when two TypeId values denote the same runtime identity, as defined below.
  5. trait.typeid.no-opsTypeId has no other operations. It exposes no type arguments, fields, or shape.
  6. trait.typeid.no-trait-queryA TypeId cannot answer whether a type implements a trait, and nothing can be constructed or called through it.
use std.inspect.TypeId

fn same_type() -> bool:
    TypeId::of::[i32]() == TypeId::of::[i32]()

#Runtime Identity

  1. trait.identity.definitionTwo types have the same runtime identity when they are the same declaration applied to type arguments that have the same runtime identity.
  2. trait.identity.normalizeRuntime identity is compared after transparent aliases are expanded.
  3. trait.identity.declarationsFor this rule the primitive types, string, List, Map, each tuple arity, and Any count as declarations, and void is the tuple of arity zero.
  4. trait.identity.trait-valueA trait value type is its trait declaration applied to its arguments.

The rules have these consequences:

Rule Types Runtime identity
trait.identity.aliasAlias a transparent alias and its target the same
trait.identity.newtypeNewtype a newtype and its base type different
trait.identity.reference-permissionReference permission &User and &mut User different
trait.identity.argumentsArguments Box[User] and Box[Post] different
trait.identity.no-conversion.reference-valuesNo conversion i32 and i64; User and User?; List[FsError] and List[*dyn Error] different: no numeric conversion, optional injection, or variance applies
trait.identity.modulesModules two declarations named User in different modules different

See also: Transparent Aliases And Newtypes.

use std.inspect.TypeId

fn demo() -> bool:
    TypeId::of::[List[i32]]() == TypeId::of::[List[i32]]()

#Stability And Printing

  1. trait.typeid.buildA TypeId depends only on which compiled program runs.
  2. trait.typeid.stableIts equality, hash, and printable name are therefore the same in every program instance, process, and run of one build.
  3. trait.typeid.cross-buildThey carry no promise across builds.
  4. trait.typeid.boundaryA TypeId is not boundary-safe.
  5. trait.typeid.nameThe printable name, produced by Display, spells the type canonically, as the table below defines.
  6. trait.typeid.name.no-parseThe name is for people and logs; nothing parses it back into a type.
Rule Type Printable name
trait.typeid.name.preludePrelude name a prelude name written as it is, as in i32, string, List[string], or Map[string, i32]
trait.typeid.name.qualifiedOther declaration every other nominal declaration, including the trait of a trait value type its absolute qualified name, as in std.error.Error
trait.typeid.name.packagePackage declaration a declaration in a package the package name from hd.toml with each - written _, then the module path and the name, as in acme_shop.model.User
trait.typeid.name.single-fileSingle-file declaration a declaration in a single-file program the file's stem, then the name, as in shop.User in shop.hd
trait.typeid.name.referenceReference &T or &mut T & or &mut followed by the canonical name of T
trait.typeid.name.counted-referenceCounted reference *T or *mut T * or *mut followed by the canonical name of T
trait.typeid.name.optionOption Option[T] T?
trait.typeid.name.tupleTuple a tuple (A, B); the empty tuple, void included, is ()
trait.typeid.name.separatorSeparator elements and type arguments separated by ,

Note. A tag that must persist is an explicit value with its own versioning.

use std.inspect.TypeId

fn demo() -> string:
    "${TypeId::of::[i32]()}"   # "i32"

See also: Runtime Boundary, Wasm Boundary.

#Inspectable Types

The compiler supplies Inspectable for exactly the inspectable types:

Rule Inspectable type
trait.inspectable.primitivePrimitive the primitive types and string
trait.inspectable.declaredDeclared a struct, enum, or newtype declared at module level, public or private, applied to inspectable type arguments
trait.inspectable.collectionsCollection List[T] and Map[K, V] with inspectable type arguments, and tuples of inspectable elements
trait.inspectable.reference-typeReference a reference whose referent is inspectable or is a trait value type
trait.inspectable.parameterType parameter a type parameter bounded by Inspectable or by a trait that has it as a supertrait
  1. trait.inspectable.exactThe compiler supplies Inspectable for exactly the types in the table above.
  2. trait.inspectable.optionThe declared case includes Option and Result, so T? is inspectable when T is.
  3. trait.inspectable.fieldsWhat the fields hold does not matter. A struct with a function-typed field, and a newtype over a function type, are inspectable, because identity is the declaration.
  4. trait.inspectable.argument-examples.reference-valuesList[*dyn Display], Result[void, FsError], and Map[string, *dyn Any] are inspectable.
use std.inspect.TypeId

fn demo() -> string:
    "${TypeId::of::[List[i32]]()}"   # inspectable: a collection of an inspectable argument

#Types That Are Not Inspectable

These types are not inspectable, as values or as type arguments:

Rule Not inspectable
trait.inspectable.not.functionFunction function types, spelled or as the constructors Fn and SuspendFn from std.function, closures, and function values
trait.inspectable.not.suspendSuspension Suspend[T] and suspension frames
trait.inspectable.not.localLocal declaration struct, enum, newtype, and trait declarations local to a block suite
trait.inspectable.not.neverNever never
trait.inspectable.not.argumentArgument a type applied to a non-inspectable argument, such as Box[fn() -> i32]
trait.inspectable.not.parameterUnbounded parameter a type parameter without an Inspectable bound
Error example
use std.inspect.Inspectable

fn erase() -> void:
    let erased: &dyn Inspectable = fn(x: i32) -> i32: x + 1   # error: type-mismatch
    pass

#Erasure To Inspectable

A concrete reference converts to &dyn Inspectable by an expected type.

  1. trait.erase.expected.reference-valuesA concrete reference converts to &dyn Inspectable when its referent is inspectable, by Trait Value References.
  2. trait.erase.no-castThere is no cast operator.
  3. trait.erase.child-implA trait that extends Inspectable still needs its own explicit implementation; only its Inspectable part is supplied. Converting a value without one is an error. Error: type-mismatch.
  4. trait.erase.recorded.reference-valuesThe recorded type is the static type of the referent at the erasure site.
  5. trait.erase.parameterFor a value of a type parameter T < Inspectable, the recorded type is the type T is instantiated with, which the bound supplies at run time.
  6. trait.erase.builtA value built from T, such as a Box[T], records Box applied to that type.
  7. trait.erase.bound-required.reference-valuesErasing a reference to a type parameter requires an Inspectable bound on its referent type. A reference without that evidence is not assignable to &dyn Inspectable. Error: type-mismatch.
  8. trait.erase.widen.reference-valuesWidening a dynamic value of a trait that extends Inspectable to Inspectable is ordinary supertrait widening, by trait.dyn.ref.widen.
  9. trait.erase.keepsThe widened value keeps its recorded concrete type. Nothing is wrapped twice, including when a type parameter is instantiated with such a trait value type.
use std.inspect.{Inspectable, TypeId}

struct Box[T]:
    value: T

fn erase[T < Inspectable](value: T) -> *dyn Inspectable:
    &Box { value: value }

fn is_int_box(value: &dyn Inspectable) -> bool:
    value.runtime_type() == TypeId::of::[Box[i32]]()

fn read_box(value: &dyn Inspectable) -> i32:
    match value.downcast_ref::[Box[i32]]().map(fn(found): *found):
        .Some(found) => found.value
        .None => 0

With let one: i32 = 1, erase(one) records Box[i32], so is_int_box returns true for it and read_box returns 1. erase(1) records Box[usize], by the default type. erase("one") records Box[string], and both functions take their other branch.

Error example
use std.inspect.Inspectable

fn erase[T](value: T) -> *dyn Inspectable:
    value  # error: type-mismatch

See also: Assignability And Coercion.

#Recovering A Concrete Type

  1. trait.downcast.referencevalue.downcast_ref::[T]() on &dyn Inspectable returns .Some of a *T exactly when its recorded concrete type has T's runtime identity, and .None otherwise.
  2. trait.downcast.mutable-referencevalue.downcast_mut::[T]() on &mut dyn Inspectable performs the same test and returns Option[*mut T].
  3. trait.downcast.reference-identityThe recovered reference refers to the original place, so is holds between it and the reference erased to the trait value.
  4. trait.downcast.exact.reference-valuesType arguments must match exactly: an erased Box[i32] is not a Box[i64], and an erased List[FsError] is not a List[*dyn Error].
  5. trait.downcast.no-conversionNo variance, numeric conversion, optional unwrapping, newtype unwrapping, or supertrait search takes place.
  6. trait.downcast.optional-referenceAn erased reference to User? is recovered with target User?, giving Option[*User?], and never with target User.
  7. trait.downcast.copy-through-referenceDereferencing the recovered reference with *found copies its value under the ordinary value-copy rules.
use std.inspect.Inspectable

struct User:
    name: string

fn user_name(value: &dyn Inspectable) -> string:
    match value.downcast_ref::[User]():
        .Some(user) => user.name
        .None => "not a user"

fn number(value: &dyn Inspectable) -> i32:
    match value.downcast_ref::[i32]():
        .Some(found) => *found
        .None => -1

Why. Recovery preserves the erased reference; copying remains the ordinary *found operation for structs, enums and other values.

#Ordinary Rules For Recovery

  1. trait.downcast.reference-methods.reference-valuesdowncast_ref and downcast_mut are reference-receiver methods in the inherent blocks of dyn Inspectable and dyn Error.
  2. trait.downcast.reference-receivers.reference-valuesThe ordinary reference-receiver rules govern both recovery methods.
  3. trait.downcast.evidenceThe Inspectable evidence for T, passed with each call like the evidence for any bound, carries the runtime identity of T.
  4. trait.downcast.pass-reference-targetA generic function passes its target through an Inspectable bound, as in fn get[T < Inspectable](value: &dyn Inspectable) -> (*T)?.
  5. trait.downcast.shared-resultdowncast_ref returns a shared reference, whose immediate referent cannot be written. Error: not-writable.
  6. trait.downcast.mutable-receiverdowncast_mut takes &mut self, so calling it through a shared reference is an error. Error: not-writable.
  7. trait.downcast.targetThe target T is written, or inferred from the expected type, at the call.
  8. trait.downcast.visibilityThe target must be nameable there under ordinary visibility, so a private type of another module cannot be recovered outside it.
  9. trait.downcast.boundA target that fails a bound is an error. Error: unsatisfied-trait-bound.
  10. trait.downcast.concrete-targetThe target is the referent's type; a reference-valued referent requires its reference type as the target.
use std.inspect.Inspectable

fn get[T < Inspectable](value: &dyn Inspectable) -> (*T)?:
    value.downcast_ref()

Note. Code first converts a concrete reference to &dyn Inspectable or &dyn Error, then calls the inherent recovery method.

#Limits Of Runtime Identity

  1. trait.limit.no-trait-testsNo trait tests. A test compares two runtime identities. Nothing asks whether a value implements a trait, and a dynamic value of one trait is never converted to an unrelated trait.
  2. trait.limit.no-requirement-keysNo inspectable requirement keys. A trait that is Inspectable or has it as a supertrait is never a requirement key. A provider value therefore cannot be tested to recover a concrete provider.
  3. trait.limit.signatures.reference-valuesVisible in signatures. Only a value of an inspectable type can be erased to &dyn Inspectable. A value of an unbounded type parameter, of Any, or of a trait value type whose trait does not extend Inspectable cannot.
  4. trait.limit.signatures.branchA function can therefore branch on a value's type only when a parameter type names Inspectable or a trait extending it. A parameter bounded by one of them also qualifies.
  5. trait.limit.reference-deterministicDeterministic. runtime_type, the TypeId operations, and downcast_ref are pure functions of the build and the value, and call no provider.
use std.inspect.Inspectable

struct User:
    name: string

fn demo(value: &dyn Inspectable) -> string:
    match value.downcast_ref::[User]():
        .Some(user) => user.name   # branches only because the parameter names Inspectable
        .None => "not a user"

See also: Requirement Rows.

#Embedding And Trait Satisfaction

Embedding never grants trait conformance: the outer type must declare its own implementation.

  1. trait.embed.no-conformanceEmbedding never grants trait conformance: an outer type implements a trait, and satisfies its bound, only through its own implementation.

An implementation that reuses an embedded type's behavior forwards to it explicitly, as in fn label(self) -> string: self.Base.label(), or with by. A bodyless implementation of a trait with a required method is therefore missing-trait-method, even when an embedded type has a matching method:

Error example
trait Describe:
    fn describe(self) -> string

struct Label:
    text: string

impl Label:
    pub fn describe(self) -> string: self.text

struct Page:
    Label

impl Describe for Page  # error: missing-trait-method

Note. A forwarding implementation is called as Trait::method(x), as in Describe::describe(service), and the embedded type's method through its path, as in service.Logger.describe(). Where the trait is available, a dot call meets both methods and is ambiguous-method, as names.method-lookup.ambiguous states.

See also: Trait Delegation, Data Embedding.

#Trait Methods Of Embedded Types

An embedded type's trait methods are not promoted, as names.promote.member states. If Label implements Describe and Page embeds Label, page.describe() never calls Label's implementation, and Page satisfies no Describe bound unless Page itself implements Describe:

Error example
trait Describe:
    fn describe(self) -> string

struct Label:
    text: string

impl Describe for Label:
    fn describe(self) -> string:
        self.text

struct Page:
    Label

fn invalid(page: Page) -> string:
    page.describe()  # error: unknown-method

Note. For the error revamp: a Page may have no describe of its own, no promoted one, and no available trait method with that name. Then the unknown-method message should suggest page.Label.describe().

See also: Member Resolution.

#Trait Delegation

A trait implementation may delegate the trait to an embedded field of its target by naming the field after by:

trait Describe:
    fn describe(self) -> string
    fn headline(self) -> string:
        "* " + self.describe()

struct Logger:
    name: string

impl Describe for Logger:
    fn describe(self) -> string:
        self.name

struct Service:
    Logger
    port: i32

impl Describe for Service by Logger

struct Worker:
    Logger

impl Describe for Worker by Logger:
    fn headline(self) -> string:
        "worker " + self.Logger.headline()
  1. trait.by.formA trait implementation may delegate the whole trait to an embedded field of its target by naming the field after by, as in impl Trait for C by E.
  2. trait.by.valid-partIn impl Trait for C by E, where E is not Structure, C must be a struct, and E must name one of its embedded fields.
  3. trait.by.part-implThe type of that field, with C's type arguments substituted, must implement the same instantiation of Trait.
  4. trait.by.invalidOtherwise the implementation is an error, reported on the line of by. Error: invalid-delegation.
  5. trait.by.structureimpl Trait for C by Structure is never a delegation. It declares a derivation template or a derivation block, as Typed Derivation defines.
  6. trait.by.no-traitA header without a trait never delegates: impl C by Structure declares a derivation block with no trait.
  7. trait.by.no-trait.errorimpl C by E without a trait, where E is not Structure, is an error, reported on the line of by. Error: invalid-delegation.
Error example
trait Describe:
    fn describe(self) -> string

struct Logger:
    name: string

impl Describe for Logger:
    fn describe(self) -> string:
        self.name

struct Service:
    Logger
    port: i32

impl Describe for Service by port  # error: invalid-delegation
impl Service by Logger  # error: invalid-delegation

#Generated Methods

  1. trait.by.generatedFor each method m of Trait with a receiver, defaults included, that the body does not write, the implementation has a forwarding method. It has m's signature, so its inputs follow m's inputs tuple, a rest element included. Its body is Trait::m(self.E, arguments...), which passes a vararg on as a spread, and it is checked as if written.
  2. trait.by.writtenA method written in the body replaces the generated one and follows the rules of Implementation Declarations, as does any other member of the body.

impl Describe for Service by Logger above therefore means:

impl Describe for Service:
    fn describe(self) -> string:
        Describe::describe(self.Logger)

    fn headline(self) -> string:
        Describe::headline(self.Logger)

Note. The forwarding call runs the embedded field's implementation with the field as its receiver. So a default body there calls the field's methods, never the delegating implementation's: there is no overriding. A &mut self method forwards through self.E, which is writable through &mut self.

See also: Data Embedding, Member Resolution.

#Delegated Associated Items

  1. trait.by.assoc-typesAssociated types take the embedded field's bindings: each associated type of the delegating implementation is bound to the type that the field's implementation binds.
  2. trait.by.assoc-bindingAn associated type binding in the body is an error. Error: invalid-delegation.
  3. trait.by.assoc-fnAssociated functions have no receiver to forward through and are never generated. The body writes each one, as in any implementation, unless the trait gives it a default. A missing one is an error. Error: missing-trait-method.
Error example
trait Named:
    fn name(self) -> string
    fn kind() -> string

struct Logger:
    label: string

impl Named for Logger:
    fn name(self) -> string:
        self.label

    fn kind() -> string:
        "logger"

struct Service:
    Logger

impl Named for Service by Logger  # error: missing-trait-method

#Delegation Is An Ordinary Implementation

  1. trait.by.ordinaryOtherwise a delegating implementation is an ordinary trait implementation, under every rule for one. Those are Implementation Ownership, Overlap, trait visibility, bounds, dynamic trait values, and Method Resolution.
trait Describe:
    fn describe(self) -> string

struct Logger:
    name: string

impl Describe for Logger:
    fn describe(self) -> string:
        self.name

struct Service:
    Logger
    port: i32

impl Describe for Service by Logger

#Default-Method Conflicts

A type that implements two traits with same-named default methods makes a dot call of that name ambiguous where both traits are available.

  1. trait.conflict.ambiguous-availableA type may implement two traits whose default methods share a name. When both traits are available, a dot call of that name is an error. Each implementation is still valid alone. Error: ambiguous-method.
  2. trait.conflict.inherentThe type may define an inherent method to provide its ordinary dot-call behavior.
  3. trait.conflict.qualifiedThe caller may instead use Trait::method(value, ...) to select one implementation.
Error example
trait Named:
    fn label(self) -> string:
        "named"

trait Tagged:
    fn label(self) -> string:
        "tagged"

struct User:
    name: string

impl Named for User
impl Tagged for User

fn invalid(user: User) -> string:
    user.label()  # error: ambiguous-method

#Unsupported Trait Extensions

  1. trait.unsupported.listThe language has no specialization, negative implementations, implicit structural conformance, trait-to-trait assertions, or type tests other than exact-type recovery from Inspectable values.
  2. trait.unsupported.abiDynamic trait-value representation is an ABI detail and must preserve the dispatch semantics in this chapter.
Error example
@derive(Eq)
struct Badge:
    text: string

impl Eq for Badge:   # error: overlapping-impl
    fn eq(self, other: Badge) -> bool:
        self.text == other.text

See also: Runtime Type Identity.