Skip to content

Generics And Traits ​

This page covers generic declarations, inference and explicit specialization, traits, implementations, and how Aura dispatches trait methods and operators.

A generic declaration is parameterized over types. A trait is a nominal interface: it names a set of methods, and a type satisfies it only through an impl. Traits provide generic bounds, method dispatch, operator dispatch, supertrait requirements, and try error conversion.

Aura has no structural typing. A type whose methods happen to have matching names does not satisfy a trait. It needs a visible, applicable impl.

Generic Declarations ​

Classes, enums, functions, methods, and implementation blocks can declare type parameters:

Aura
class Box[T]:
    value: T

enum MaybePair[T]:
    One(T)
    Two(T, T)

def identity[T](value: own T) -> T:
    return value
  • Type parameter names must be unique within their declaration.
  • Self is reserved and cannot be declared as a type parameter.
  • A generic use must supply exactly the declared number of type arguments.
  • Generic arguments are invariant. Aura never widens them implicitly or converts them structurally.

A bound follows a type parameter after :. Join bounds with + to require every one of them:

Aura
def use_value[T: Display + Score](value: T) -> int32:
    print(value.display())
    return value.score()

Classes and enums can carry bounds too. The checker enforces them when it resolves a construction, and when bounded generic operations use the specialized value:

Aura
class NamedBox[T: Named]:
    value: T

enum MaybeNamed[T: Named]:
    Some(T)
    Empty

Grammar gives the exact parameter-list forms.

Inference And Specialization ​

A generic call infers its type arguments by unifying the argument types with the parameter type patterns. An expected result type, when one is available, adds constraints. Generic class and enum construction infers the same way from the supplied fields or payloads and an expected constructed type.

Aura
boxed = Box(value=7)          # Box[int64]
value = identity("Aura")   # str

Every declared type parameter must resolve. The checker does not invent a type for a parameter that appears in no supplied value and no expected context.

Parameter ownership is fixed at the generic declaration. An unconstrained T is not assumed copyable, so a bare value: T is a shared borrow. It stays a shared borrow even when a call later specializes T to a copy type. Write value: own T when the body consumes, stores, or returns the argument.

Explicit specialization fixes the type arguments:

Aura
boxed = Box[int64](value=42)
value = identity[int64](42)
ok = Result[int32, str].Ok(7)

Explicit arguments must have the exact arity and satisfy every substituted bound. Specialization and indexing share bracket syntax. Grammar gives the parser rules that tell them apart.

Type Parameters As Union Members ​

A declared type parameter can be a union member, and a generic alias can name such a union. Substitution renormalizes the union. With V = int64 | None, the union V | None is int64 | None. With V = None, it is unit None. A generic union never keeps a phantom outer tag to tell those results apart.

Aura
type MaybeValue[V] = V | None

def absent[V]() -> V | None:
    return None

def present[V](value: own V) -> V | None:
    return value

def main():
    first: MaybeValue[int64] = present[int64](4)
    if first is not None:
        print(first + 1)
    print(absent[int64 | None]() is None)
    print(present[int64 | None](None) is None)

This prints 5, true, and true.

Inference binds a union-member parameter to the rest of the argument. Every concrete member of the parameter's union must be a member of the argument, and the remaining members bind the parameter:

  • V | None infers V = Dog from Dog or from Dog | None.
  • V | None infers V = int64 | str from int64 | str | None.

The checker rejects two cases with AU2010. One is an argument that leaves nothing for the parameter, such as None. The other is a union whose members could belong to more than one parameter. Specialize explicitly in both cases. The checker does not invert normalization.

Inside a generic body, the checker cannot assume that V, W, and None are distinct:

  • On V | None, value is None narrows to None and value is not None narrows to V. That V is only the non-None refinement. The test does not prove that V itself excludes None.
  • A None test on a bare V value is decided at run time. So is a None test on a union whose only None could come from a type parameter.
  • A type arm case V as inner is rejected with AU2013, because V is not proved to be one member disjoint from every other arm. Write case None and a catch-all instead.

Concrete union properties come from the concrete members after specialization. An unresolved member grants nothing without a bound.

Inferred Clone-Safety Obligations ​

Aura infers clone-safety obligations from the clone-producing operations in a callable's body. When such an operation works on an unresolved type parameter, the checker accepts it and records which declared parameters must be safe to clone. A concrete call discharges those obligations after substitution.

A type is safe to clone when duplicating it cannot duplicate random.Rng state or a live resource such as fs.File through an ordinary class, enum, or collection path. Task[T] and Queue[T] handles stop this traversal, because an allowed handle copy does not observe or copy T. A clone barrier is not an escape from the task-boundary rule. Queue payloads and task results must separately be Transfer, and Task[T] is non-copy when T is not repeatable.

Obligations travel with the callable:

  • A generic-to-generic call propagates the obligation to the caller.
  • Inference runs to a fixed point and does not depend on declaration order.
  • Imported functions and methods keep the resulting contract.
  • The same rules apply to ordinary, inherent, associated, task-target, trait, operator, and From calls.

There is no source annotation for this obligation in Aura 0.6.

When a type is concrete, a substitution containing random.Rng is rejected with AU3007. A concrete type whose clone safety cannot be proved is also rejected, conservatively. Moving, removing, receiving, or rearranging one owned value does not produce a clone and adds no obligation.

Verified Clone-Safety Contracts ​

These examples pin the observable boundary. The trait examples depend on the conformance rule in Implementation Method Conformance.

A generic clone helper is valid for a safe specialization:

Aura
def duplicate[T](values: list[T]) -> list[T]:
    return values.copy()

def main() -> int32:
    values = [1, 2]
    print(duplicate(values))
    return 0

The same callable rejects an unsafe concrete specialization:

Aura
import random

def duplicate[T](values: list[T]) -> list[T]:
    return values.copy()

def reject(values: list[random.Rng]) -> list[random.Rng]:
    return duplicate(values)

The requirement survives a generic-to-generic call:

Aura
import random

def duplicate[T](values: list[T]) -> list[T]:
    return values.copy()

def forward[T](values: list[T]) -> list[T]:
    return duplicate(values)

def reject(values: list[random.Rng]) -> list[random.Rng]:
    return forward(values)

A signature-only trait method does not let an implementation add a hidden requirement:

Aura
trait Copier[T]:
    def copy_values(self) -> list[T]

class Wrapper[T]:
    values: list[T]

impl[T] Copier[T] for Wrapper[T]:
    def copy_values(self) -> list[T]:
        return self.values.copy()

A trait default body can establish the requirement for safe specializations:

Aura
trait Duplicator[T]:
    def duplicate(self, values: list[T]) -> list[T]:
        return values.copy()

class Marker[T]:
    value: T

impl[T] Duplicator[T] for Marker[T]:
    pass

def main() -> int32:
    marker = Marker(0)
    values = [4, 5]
    print(marker.duplicate(values))
    return 0

The same contract rejects its unsafe specialization:

Aura
import random

trait Duplicator[T]:
    def duplicate(self, values: list[T]) -> list[T]:
        return values.copy()

class Marker[T]:
    value: T

impl[T] Duplicator[T] for Marker[T]:
    pass

def reject(marker: Marker[random.Rng], values: list[random.Rng]) -> list[random.Rng]:
    return marker.duplicate(values)

Trait Declarations ​

A trait declares a nominal method contract:

Aura
trait Greeter:
    def greet(self) -> str

A trait method whose body is only a docstring is still signature-only. See Docstrings And API Docs.

A trait method is either signature-only, ending at the newline, or has a default body after ::

Aura
trait Named:
    def name(self) -> str

    def label(self) -> str:
        return "name=" + self.name()

A marker trait contains pass and no required methods:

Aura
trait Marker:
    pass

Trait names and method names must be unique in their scopes. Trait type parameter lists use the plain parameter form:

Aura
trait Mapper[T]:
    def map(self, value: own T) -> T

A trait method's own generic parameters can have bounds. Ordinary trait method parameters cannot have defaults.

An obligation inferred from a trait default body is part of the trait method's contract. It is substituted through Self, the trait arguments, and the method type arguments, for every implementation and every form of dispatch. A signature-only trait method has no inferred clone-safety obligation.

A trait is private to its defining module unless it is declared public trait. Implementation blocks have no exported name of their own and cannot be prefixed with public. Their methods become available through the implemented public trait or type when the implementation is loaded.

Self ​

Self denotes the implementing or enclosing concrete class specialization. It is available in the supported type positions of class, trait, and implementation methods:

Aura
trait Combine:
    def combine(self, other: Self) -> Self
  • Self takes no type arguments.
  • Self is not a global type. An unrelated top-level function cannot use it.
  • Inside a trait declaration, Self starts as a placeholder. Inside an implementation, it is replaced by the implementation target.

Implementations ​

An implementation attaches one trait specialization to one target type pattern:

Aura
class Person:
    name: str

impl Greeter for Person:
    def greet(self) -> str:
        return "hello " + self.name

Implementations can be specialized or generic:

Aura
impl Mapper[int32] for Doubler:
    def map(self, value: own int32) -> int32:
        return value * self.factor
Aura
impl[T] Mapper[T] for Box[T]:
    def map(self, value: own T) -> T:
        return value
Aura
impl Displayable for Box[str]:
    def display(self) -> str:
        return self.value.clone()

Target rules:

  • The target must have a concrete or generic named outer type, such as Box[T]. A bare type parameter target, as in impl[T] Trait for T, is rejected.
  • Implementation type parameters can have bounds.
  • Every parameter used by the target or trait pattern must resolve during applicability checking.

Overlap rules:

  • Two implementations with exactly the same trait specialization and target are duplicates. The checker rejects them.
  • More general and more specialized overlapping patterns can coexist.
  • Dispatch selects the unique applicable implementation with the greatest structural specificity. Equal-best matches are ambiguous and rejected.
  • Source order never breaks a tie.

A trait from an imported module can be named through its module, in an implementation, a bound, or a supertrait:

Aura
import shapes

impl shapes.Make for Gadget:
    def make(seed: int64) -> Gadget:
        return Gadget(size=seed)

def build[T: shapes.Make](seed: int64) -> T:
    return T.make(seed)

The qualified name and a from shapes import Make name the same trait. A module alias works too, as in impl sh.Make for Gadget after import shapes as sh.

An implementation with no body, such as impl json.Codec for Point, asks the compiler to derive the methods. Only json.Codec can be derived, and the checker rejects any other bodyless implementation with AU2046. See Derived Codecs.

Aura 0.6 imposes no separate orphan rule. An implementation must still refer to known, visible types and traits. It takes part only where it is present in the loaded module or package context.

Trait Methods On Unions ​

A union value dispatches a trait method when every member implements the same trait specialization with one contract. One contract means the same receiver mode, parameter modes and types, and owned result type after each member is substituted for Self. The active member's implementation runs:

  • A mut self method mutates the payload in place, and the tag stays fixed.
  • An own self method consumes the union.

A result written as Self that would differ per member is not one contract. Match the member and declare the result union explicitly. There is no duck typing over common method names, no impl for an anonymous union, and no inferred trait intersection.

The same all-member rule decides whether a union satisfies a trait bound. A union type argument satisfies T: Named when every member implements that specialization, and every method the bound exposes, supertraits included, has one coherent contract across the members.

  • A member without the implementation reports AU2002.
  • Members whose contracts differ report AU2999, the same code as a direct call on the union.

A bounded callee then dispatches each trait call on the active member, exactly as the direct call would.

Implementation Method Conformance ​

An implementation can define only methods that belong to the trait. It must provide every signature-only method. It inherits a default method that it omits, and it can override one.

For each explicitly implemented method, conformance compares:

  • receiver presence and passing mode: shared self, consuming own self, mut self, or none
  • ordinary parameter count and substituted types
  • each ordinary parameter's resolved owned, shared, or mutable access mode
  • owned return type
  • the trait method's substituted clone-safety obligations

Ordinary parameter names can differ between the trait and the implementation when their positions and types still match.

The checker rejects the following before any body runs:

  • extra methods
  • missing required methods
  • receiver mismatches
  • signature mismatches
  • default ordinary arguments added by an implementation method

An explicit implementation MUST NOT strengthen its trait method's clone-safety contract. Its body can rely on obligations that the trait method already inferred. It cannot add a requirement that bound-based callers cannot see. Aura 0.6 has no explicit clone-safety annotation, so generic clone-producing behavior belongs in a trait default body. An implementation that adds such a requirement is rejected with AU3007.

An impl targeting any builtin type MUST NOT explicitly define or inherit a trait method whose name is a builtin member of that target. This covers:

  • the runtime handles Queue[T], Task[T], TaskGroup, random.Rng, fs.File, and the net and process handles
  • the builtin value types, such as str, list[T], dict[K, V], set[T], Duration, and the scalar types

Builtin member names are reserved for their runtime operation. A collision reports AU2006. Rename the trait method to fix it. The check runs after default trait methods are inherited. A trait method whose name does not collide implements and dispatches normally on a builtin target.

Trait Method Dispatch ​

For a concrete value, member lookup considers inherent class methods and applicable visible trait implementations. The selected method keeps its declared receiver and argument ownership behavior.

A trait method selected for a concrete value also binds as a method value. Outside a call, value.method is a closure over the receiver with the method's complete contract. The receiver rules are in Closures.

For a type parameter, only the methods justified by its declared bounds are available:

Aura
def say_hello[T: Greeter](value: T):
    print(value.greet())

A specialized trait bound supplies its type arguments:

Aura
def apply[M: Mapper[int32]](mapper: M, value: int32) -> int32:
    return mapper.map(value)

A call is ambiguous and rejected when several bounds, or several equally specific implementations, expose an indistinguishable applicable method.

Traits can also declare associated methods without self:

Aura
trait Factory:
    def make() -> int32

impl Factory for Widget:
    def make() -> int32:
        return 7

value = Widget.make()

Associated trait methods follow the same rules as receiver methods. Concrete and bound-based dispatch enforce the same substituted clone-safety contract.

Associated Methods Through A Type Parameter ​

A generic function can call an associated method through its type parameter. The bound must provide the method, and the method must not take self:

Aura
trait Parse:
    def parse(text: str) -> Result[Self, str]

class Port:
    number: int64

enum Mode:
    Read
    Write

impl Parse for Port:
    def parse(text: str) -> Result[Port, str]:
        match parse_int64(text):
            case Result.Ok(number):
                return Result.Ok(Port(number=number))
            case Result.Err(_):
                return Result.Err(f"not a port: {text}")

impl Parse for Mode:
    def parse(text: str) -> Result[Mode, str]:
        if text == "read":
            return Result.Ok(Mode.Read)
        if text == "write":
            return Result.Ok(Mode.Write)
        return Result.Err(f"not a mode: {text}")

def parse_all[T: Parse](texts: list[str]) -> Result[list[T], str]:
    mut values: list[T] = []
    for text in texts:
        values.append(try T.parse(text))
    return Result.Ok(values)

def main() -> int32:
    match parse_all[Port](["80", "443"]):
        case Result.Ok(ports):
            print(ports[1].number)
        case Result.Err(message):
            print(message)
    match parse_all[Mode](["read", "append"]):
        case Result.Ok(modes):
            print(modes.len())
        case Result.Err(message):
            print(message)
    return 0

T.parse(text) calls the implementation for the concrete type at each call site. parse_all[Port] uses Port.parse, and parse_all[Mode] uses Mode.parse. A generic function that passes its own type parameter on, such as build[T](seed), works the same way.

The compiler makes one copy of such a function for each concrete type list it is called with. Generic functions that make no such call keep one shared body.

These rules apply:

  • The call must be in a module-level function. A method of a class or an implementation cannot make it.
  • The associated method cannot declare its own type parameters.
  • A function that makes the call, directly or through another generic function, cannot be a task target. Start the task with a function that calls it.
  • T.method without a call is not a value.

Supertraits ​

A trait can require one or more supertraits:

Aura
trait Labelled: Named:
    def label(self) -> str:
        return "name=" + self.name()

The second colon ends the header. Separate multiple supertraits with commas.

impl Labelled for User is valid only when User also satisfies Named through an applicable implementation. Implementing the child trait does not create the parent implementation. A child bound makes the supertrait methods available, and default child methods can call them.

Supertrait types must name known traits with exact arity. Bound and dispatch checking closes the requirements transitively.

Operator Traits ​

When no builtin numeric or string operator rule applies, an operator requests a trait method:

Source operatorTrait method
left + rightAdd.add
left - rightSub.sub
left * rightMul.mul
left / rightDiv.div
left // rightFloorDiv.floor_div
left % rightMod.mod
-valueNeg.neg
not valueNot.not
<, <=, >, >=Ord.lt, Ord.le, Ord.gt, Ord.ge

Builtin rules come first:

  • Builtin numeric // and the heterogeneous builtin Duration // int64 rule take precedence over trait dispatch. Otherwise // and //= request an applicable FloorDiv.floor_div implementation.
  • / on equal integer operands is rejected with the integer-division teaching diagnostic. It is not dispatched to Div.div. / still requests Div.div for an applicable non-numeric user type.
  • The divisor-sign rule for % describes builtin numeric remainder. Mod.mod on a user type has the semantics of that implementation.

These declarations show the maintained generic shapes:

Aura
trait Add[Rhs, Out]:
    def add(self, rhs: Rhs) -> Out

trait FloorDiv[Rhs, Out]:
    def floor_div(self, rhs: Rhs) -> Out

trait Neg[Out]:
    def neg(self) -> Out

trait Ord[Rhs]:
    def lt(self, rhs: Rhs) -> bool
    def le(self, rhs: Rhs) -> bool
    def gt(self, rhs: Rhs) -> bool
    def ge(self, rhs: Rhs) -> bool

Sub, Mul, Div, and Mod use the same binary Rhs, Out shape as Add and FloorDiv. Not uses the unary Out shape. Ordering methods must return bool.

Some operators never use traits:

  • and and or do not dispatch through traits.
  • Builtin == and != do not use an equality trait in Aura 0.6. This includes recursive structural tuple equality, which a trait implementation cannot override.
  • Builtin operations take precedence wherever their concrete value rule applies.

An operator that selects a trait method also enforces that method's substituted clone-safety obligations.

The Display Trait ​

Every module sees a prelude trait that customizes how a value renders. The prelude declares it as:

text
trait Display:
    def display(self) -> str

Implement it for a class or enum that the same module declares:

Aura
copy class Money:
    cents: int64

impl Display for Money:
    def display(self) -> str:
        return f"${self.cents // 100}.{self.cents % 100:02}"

class Invoice:
    total: Money
    lines: list[Money]

def main():
    price = Money(cents=1250)
    print(price)
    print(f"due: {price}")
    print(str(price) == price.display())
    print(Invoice(total=price, lines=[price, Money(cents=5)]))

The program prints $12.50, due: $12.50, true, and Invoice(lines=[$12.50, $0.05], total=$12.50).

print(value), str(value), and an f-string field without a format specification all call display. So does the rendering of a value nested inside a list, tuple, set, dictionary, class field, enum payload, or union. A type without an implementation keeps its structural rendering.

An implementation has these rules. AU2043 reports each violation:

  • The target is a class or enum declared in the implementing module. A builtin type, a tuple, a union, a type parameter, and another module's type are rejected.
  • A generic target covers every specialization with distinct type parameters, as in impl[T] Display for Box[T]:. A specialized target such as Box[int64] and a bounded parameter such as T: Ord[T] are rejected, because one type has one rendering.
  • A type has at most one Display implementation.
  • The block defines exactly def display(self) -> str. It has a shared receiver, no parameters, no type parameters, and an owned str result.

A module that declares or imports its own item named Display uses that item instead. Implementations of a user trait named Display do not change rendering.

In an f-string, a value with a Display implementation accepts the string subset of the format grammar: fill, alignment, width, precision, and s. Precision truncates the rendered text first, then padding applies. A sign, zero padding, a thousands separator, or a numeric type code is AU2002.

A print call renders its whole line before it writes. If a display call traps, that print writes nothing.

From And try ​

When try propagates Result[T, SourceError] from a function that returns Result[U, TargetError]:

  • If the error types are equal, no trait is needed.
  • Otherwise the checker looks for an applicable impl From[SourceError] for TargetError that contains from.

The conventional contract is:

Aura
trait From[Source]:
    def from(value: own Source) -> Self

The checker enforces the selected From.from method's clone-safety obligations before it accepts the conversion. At run time the conversion runs before Result.Err is returned from the enclosing function. If no applicable conversion exists, try is rejected. See Functions.

Current Generic And Trait Boundaries ​

  • Generic arguments are invariant, and there is no general subtyping.
  • Type inference is local and contextual, not whole-program.
  • Trait and implementation methods cannot give ordinary parameters defaults.
  • Generic task targets are permitted when their callable type arguments can be resolved. Bare shared and own targets use task-owned captures. mut targets are rejected.
  • Equal-specificity overlapping implementations are an error at the use site.
  • Clone-safety obligations are inferred, not written. An explicit implementation cannot strengthen the contract inferred by its trait method.

Current Limits collects the observable syntax and implementation limits. Static Semantics has the cross-cutting type rules.

Grammar ​

Grammar has the normative productions for type parameters, bounds, explicit specialization, trait declarations, supertraits, Self, and implementation blocks.

Classes, enums, functions, methods, traits, and implementations use the declaration-specific parameter forms shown above. Trait methods are signature-only or have a default suite. Implementation methods always use ordinary method-definition syntax.

Typing Rules ​

  • Generic arguments are invariant and have exact arity.
  • Inference is local and contextual. It must resolve every declared parameter and satisfy every substituted bound.
  • A parameter that is a union member binds to the argument's remaining members. Unions are renormalized after every substitution.
  • A generic body is checked once, universally, before any specialization.
  • Trait satisfaction is nominal, through a visible applicable impl. It is never structural.
  • An implementation must conform after substitution in receiver mode, parameter modes and types, owned return type, clone-safety obligations, and supertrait requirements.
  • Dispatch selects one unique applicable implementation of greatest specificity. Equal-best matches are rejected.
  • Self denotes the enclosing or implementing concrete specialization, and only in its supported declaration contexts.

Runtime Semantics ​

Generic construction and calls use the statically resolved specialization. There is no runtime generic inference.

A union value built inside a generic body keeps the body's symbolic member layout until a concrete operation reads it. Every union operation aligns the value to the union it names, by the active member's identity. A V-typed union payload flattens into its destination without a nested wrapper. So both execution paths agree on the one normalized value.

Trait member calls and operator calls invoke the statically selected implementation. When the implementation omits a method, the trait default body runs. Source order never resolves overlapping implementations.

try invokes the selected From[Source] conversion before it constructs the enclosing Result.Err.

Traits do not create runtime reflection, dynamic method dictionaries, or implicit conversions.

Ownership And Evaluation Order ​

Parameter ownership is resolved at the generic declaration and stays stable after specialization. An unresolved bare T is shared, even when a later substitution is a copy type. own T is the explicit consuming form. Trait and implementation signatures must agree on that resolved mode.

The receiver is evaluated before the ordinary arguments. A selected method keeps its declared receiver and parameter behavior. From.from owns its source error.

No generic or trait boundary inserts a hidden clone, coercion, or ownership-mode change. Clone-producing bodies infer obligations, generic calls propagate them, and concrete dispatch discharges them after substitution.

Diagnostics ​

CodeCause
AU1101Malformed generic, trait, supertrait, specialization, or implementation syntax.
AU2001Unknown type, trait, method, or member.
AU2002Inference failure, wrong generic arity, unsatisfied bound, missing trait satisfaction, ambiguous equal-specificity dispatch, invalid specialization, or substituted type mismatch.
AU2003An operator that no builtin rule or applicable operator trait supplies.
AU2004Call argument binding, and ordinary default arguments in trait methods.
AU2005An unsupported T.method(...) call: the method takes self, declares type parameters, or is called outside a module-level function, or the function is used as a task target or with a type parameter where no concrete type reaches it.
AU2006A trait method name that collides with a builtin member. Rename the trait method.
AU2010A type parameter that a union-member argument cannot determine. Specialize explicitly.
AU2013A type arm over a type parameter. Use case None and a catch-all.
AU2043An invalid Display implementation: a foreign, builtin, tuple, union, or type-parameter target, a target that does not cover every specialization, a bounded parameter, an overlapping implementation, or a display method that does not match the trait.
AU2999Duplicate or invalid implementations, method-conformance or supertrait failure, unsupported implementation targets, and other generic or trait rejections.
AU3001Use after an owned generic value or receiver has moved.
AU3002A borrow conflict, or storing through a bare shared generic parameter.
AU3003A mutable receiver call through an immutable place.
AU3004An invalid ownership mode.
AU3007An unsafe concrete clone specialization, an unprovable concrete requirement, or an implementation that would strengthen its trait method's clone-safety contract.

A selected body keeps its own runtime diagnostics:

CodeCause
AU4001General trap.
AU4002Arithmetic overflow or underflow.
AU4003Bounds or lookup violation.
AU4004Zero divisor.
AU4005Resource or I/O failure.

Backend Support ​

MIR execution and direct native generation both implement generic functions, classes, enums, and methods, traits, supertraits, default trait bodies, generic and specialized implementations, operator dispatch, Self, and From conversion. MIR is the compiler's mid-level intermediate representation.

Both backends maintain user-trait dispatch on builtin values for noncolliding method names. That includes Queue[T], Task[T], TaskGroup, random.Rng, str, and the builtin collections. Builtin target members always keep builtin dispatch.

The checker gives lowering, analysis, and the language server one resolved specialization and implementation target, including inferred clone-safety obligations. The parity gate rejects dispatch behavior that differs between backends.

Limits And Implementation-Defined Behavior ​

Aura 0.6 has none of the following:

  • trait objects or dynamic dispatch
  • associated types or constants
  • higher-kinded parameters
  • default type arguments
  • where clauses
  • specialization annotations
  • general subtyping
  • a separate orphan-rule restriction

A bare target parameter in impl[T] Trait for T is unsupported. Equal-specificity overlaps are errors. Ordinary trait and implementation parameters cannot add defaults.

The rules above define inference and dispatch. Source order and backend implementation choices do not.

Status ​

Implemented:

  • invariant generics, local and contextual inference, and explicit specialization
  • nominal traits and bounds, supertraits, and default methods
  • associated methods called through a bounded type parameter, such as T.parse(text)
  • generic and specialized implementations with unique-most-specific dispatch
  • operator traits, Self, and From-based try conversion
  • the prelude Display trait and its rendering dispatch
  • inferred clone-safety contracts
  • type parameters as union members, renormalizing specialization, remainder inference, and universal generic body checks

Ordinary -> T return values are owned. Generic functions and methods can instead declare -> view [mut] T from origin. Trait implementations preserve the trait declaration's origin slot as well as its specialized pointee type.

Trait objects, dynamic dispatch, associated types, higher-kinded types, general subtyping, and arbitrary blanket implementation targets are unavailable.

Design Record ​

Aura 0.6.0 technical preview. Implementation baseline: 4d7e076de9ff6e2e056cdec10881d4bf421358de.