12Implement Blocks
implement adds methods to a type without modifying its original declaration. There are three forms.
12.1 Inherent Implementation
Adds methods to a type. For a struct, an inherent block is interchangeable with declaring the methods inline:
implement struct Point {
distance_to(&this, other: Point) -> f64 {
const dx: f64 = (this.x - other.x) as f64;
const dy: f64 = (this.y - other.y) as f64;
return (dx * dx + dy * dy).sqrt();
}
}
Inherent blocks are the only way to add methods to enums:
implement enum Color {
to_string(&this) -> string {
match (this) {
Color::Red => { return "red"; }
Color::Green => { return "green"; }
Color::Blue => { return "blue"; }
}
}
}
12.2 Trait Implementation
Provides the method bodies a trait requires for a specific type. Use this to make T participate in generic code that has a where T: SomeTrait bound.
implement trait Eq for i32 {
equals(&this, other: &i32) -> boolean {
return this == *other;
}
}
12.3 Implement Blocks on Primitives
Implement blocks may target primitive types, which is how the standard library hangs methods off boolean, the integer types, and string:
implement boolean {
to_i32(&this) -> i32 {
if (this) { return 1; }
return 0;
}
static default() -> boolean {
return false;
}
}
After the block is loaded, my_bool.to_i32() resolves like any other method call. Resolution is at compile time; no dynamic dispatch.
A primitive owns methods and nothing else: no type declares constants, so i32::MAX names no value and is refused (E0201), through an alias of the primitive too. An integer type's bounds are its static methods, i32::max_value() and i32::min_value().
12.4 Which Method a Call Names
A method call recv.m(...) names the one method m its arguments bind to, and no kind of method outranks another. Two methods of one name that both fit the arguments make the call ambiguous, and it is an error; in particular, a non-generic method does not win over a generic one by being non-generic:
type struct Box2 { v: i32; }
implement struct Box2 {
put(&this, x: i32) -> i32 { return x; }
put<T>(&this, x: T) -> i32 { return 0; }
}
const b: Box2 = Box2 { v: 0 };
const n: i32 = b.put(1); // error: both `put`s apply
A static method is chosen the same way, its generic overloads by the type arguments its arguments (or its turbofish) bind. With conv<U>(u: U) and conv<U>(u: U, w: U) on Plain, Plain::conv(7, 9) calls the second. A static call two overloads accept is E0154, whether both are generic or one is not, and a call none accepts is E0214.
A receiver with an inherent method m and a trait impl that also provides an m the arguments bind to makes the call ambiguous (E0156), whichever order the implement blocks were written in and whether the receiver or either method is generic. The path forms are the same call and the same tie: T::m(recv, ...), T::m() for a static method, and T::m as a value. The caller writes the impl-qualified path (Tr for T)::m(&recv, ...) below, or renames one of them. A name two traits provide is an error as well (E0156), whether the call is written recv.m(...), T::m(recv, ...), T::m() for a static method, or T::m as a value, and never a pick by declaration or import order. The error names each candidate where it is declared and the spelling that chooses. A call made through a bound (x.m() where x: &T and where T: Tr, or the path T::m(...) and the value T::m under the same bound) names Tr::m, since T has no methods but its bounds' where the call is written — the concrete type it is later instantiated with does not change that, even when it has an inherent m or implements a second trait providing m. A parameter bounded by two traits that both provide m directs nothing, and the instantiation reports the tie. A member none of the parameter's bounds declares is refused at the member: x.m() where it is written (E0358), and the path T::m(...) and the value T::m (E0358), or (Tr for T)::m where Tr declares no m (E0233), once the body is instantiated - a generic body nothing instantiates is not refused for such a path. On a concrete receiver there is no such direction, so an inherent method and a trait's method of one name that both fit are a tie, and the call is refused.
type trait Beta { go(&this, u: i32) -> i64; }
type struct IgTn { v: i32; }
implement trait Beta for IgTn { go(&this, u: i32) -> i64 { return 2; } }
implement struct IgTn { go<U>(&this, u: U) -> i64 { return 1; } }
function via<T>(x: &T) -> i64 where T: Beta { return x.go(0); }
const ig: IgTn = IgTn { v: 5 };
const a: i64 = ig.go(7); // error[E0156]: an inherent `go` and `Beta::go` both apply
const d: i64 = IgTn::go(&ig, 7); // error[E0156]: the same tie through the path
const b: i64 = (Beta for IgTn)::go(&ig, 7); // 2
const c: i64 = via::<IgTn>(&ig); // 2: the bound names `Beta::go`
A method as a value. A method named by path without a call is a function value. A static method's value has the method's own signature; a method with a receiver takes the receiver as its first parameter: for step(&this, u: i32) -> i64, Pt::step is a (&Pt, i32) -> i64, called f(&p, 7); mut &this gives mut &Pt and this gives Pt. The value names one method, so an inherent m beside a trait's m is the same tie (E0156) and (Tr for T)::m chooses. A method generic in its own parameters, or a method of a generic owner, is taken as a value the same way, and its type arguments are inferred from the use, as a generic function's are (section 2.5): const g: (&Wrap<i32>) -> i32 = Wrap::get;. A generic owner's method with parameters of its own binds both sets: the owner's from the arguments written on the owner or the expected type, the method's from its turbofish or the expected type, so Wrap::<i32>::conv::<u8> and const k: (&Wrap<i32>, u16) -> i32 = Wrap::keep; are each one instance. A parameter nothing binds is E0307. An overloaded method is chosen as an overloaded function is: the overload whose value fits the turbofish and the expected function type, so with static methods conv<U>(u: U) and conv<U>(u: U, w: U) on Plain, const f: (u8, u8) -> u8 = Plain::conv::<u8>; takes the second. When no expected type and no turbofish choose, or more than one overload fits, the value is refused as ambiguous (E0154).
type struct Pt { v: i32; }
implement struct Pt { step(&this, u: i32) -> i64 { return (this.v + u) as i64; } }
const p: Pt = Pt { v: 5 };
const f = Pt::step; // (&Pt, i32) -> i64
const n: i64 = f(&p, 7); // 12
The impl-qualified path. (Tr for T)::m names the m that Tr's implementation for T delivers, whatever other traits provide m for T. It is one rule for every shape the tie takes: a static method, (Tr for T)::m(), which no receiver could select; a method with a receiver, (Tr for T)::m(&recv, ...), the receiver its first argument; a method taken as a value, (Tr for T)::m; and the form inside a generic body, (Tr for U)::m() with U a type parameter, which selects Tr's implementation for whatever U is instantiated with. The head reuses for exactly as implement trait Tr for T writes it - a trait, then a type by name with generic arguments if any - and is followed by :: and one member. A head whose type does not implement the trait is refused (E0306, the bound the head asserts), one whose implementation has no such member is refused at the member (E0233), and the call's arguments are checked against that implementation's signature. A generic method's own type parameters bind from the path's turbofish, (Tr for T)::m::<u8>(&recv, x), or from the arguments after the receiver, as they do for recv.m(x). The trait-qualified call Tr::m(&recv, ...) also resolves, by the receiver's type, but only where there is a receiver; the impl-qualified path is the form E0156 proposes.
type trait Beta { go(&this) -> i32; static make() -> i32; }
type trait Gamma { go(&this) -> i32; static make() -> i32; }
type struct P { v: i32; }
implement trait Beta for P { go(&this) -> i32 { return 2; } static make() -> i32 { return 2; } }
implement trait Gamma for P { go(&this) -> i32 { return 5; } static make() -> i32 { return 5; } }
const p: P = P { v: 1 };
const a: i32 = P::make(); // error[E0156]: two traits provide `make`
const b: i32 = (Beta for P)::make(); // 2
const c: i32 = (Gamma for P)::go(&p); // 5
const f: () -> i32 = (Gamma for P)::make;
implement struct Name { ... }
implement trait Trait for Type { ... }
implement u64 { ... }Adds methods to a type outside its declaration. implement struct T (or enum, or a bare primitive) opens an inherent block; implement trait Tr for T supplies a trait's methods for one type. Resolution is at compile time - no dynamic dispatch.
implement struct Name { ... }Names the struct an inherent block extends. The methods inside are interchangeable with ones declared in the struct body.
language referencetype struct PointPoint::distance_to(&this, other: Point) -> f64&this
mut &this
thisThe receiver of a method. Its first parameter declares how the method takes it: &this borrows read-only, mut &this borrows exclusively, and this / mut this move the value in and consume it. In the body, this.field reaches the members.
(parameter) other: Pointtype struct Pointconst name: Type = value;An immutable binding: it cannot be reassigned after initialisation. The annotation may be omitted when an initialiser is present - the binding takes the initialiser's concrete type, inferred locally. Mutability is opt-in through mut.
const dx: f64(parameter) other: Pointvalue as TypeExplicit conversion: between numeric types, or reinterpreting one pointer type as another. Cryo never converts implicitly, and as inserts no range checks - a narrowing cast is the programmer's responsibility.
const dy: f64return value;Leaves the current function with the given value. A function whose return type is not void must return one; a bare return; is for void. In an async function it completes the future with the value.
const dx: f64const dy: f64implement enum Name { ... }Names the enum an inherent block extends - the only way to give an enum methods.
language referencetype struct ColorColor::to_string(&this) -> stringprimitive stringNUL-terminated raw string (u8*), matching the C ABI. Length-typed text is Str / String.
match (subject) {
Pattern(x) => { ... }
_ => { ... }
}The primary discriminator: branches on enum variants, integers, ranges, and destructuring patterns, and the compiler rejects a match that misses a case. There is no fallthrough. As an expression it evaluates to the chosen arm's value, so every arm must produce the same type.
type struct Colorpattern => { body }Separates a match arm's pattern from its body. Arms do not fall through.
implement trait Trait for Type { ... }Introduces a trait implementation: the block supplies, for one concrete type, the method bodies the trait requires.
language referencetype trait Eq {
equals(&this, other: &This) -> boolean;
}Types that support value equality. equals(a, b) must be reflexive, symmetric, and transitive.
implement trait Trait for Type { ... }Names the type a trait implementation is for. The block that follows supplies the bodies the trait requires.
language referencei32::equals(&this, other: &i32) -> boolean(parameter) other: &i32(parameter) other: &i32boolean::to_i32(&this) -> i32if (condition) { ... } else if (condition) { ... } else { ... }Branches on a parenthesised boolean condition. Bodies are always braced; there is no single-statement form. Used as an expression, both arms are required and must produce the same type: const sign = if (n < 0) { -1 } else { 1 };.
static name(param: Type) -> Ret { ... }A method that belongs to the type rather than an instance, called as Type::name(..). For structs, static new(..) returning a struct literal is the idiomatic constructor.
static boolean::default() -> booleanfalseThe boolean literal. Booleans are a distinct 1-byte type, not interchangeable with integers; if and while conditions must be boolean.
type struct Name { ... }
type enum Name { ... }
type trait Name { ... }
type union Name { ... }Begins a type declaration; the keyword that follows says which kind. On its own, type Name = Existing; declares an alias.
type struct Name {
field: Type;
method(&this) -> Ret { ... }
}A value type with named fields and optional methods. Structs live on the stack and are passed by value. Fields are public by default and may carry = default values; methods take their receiver as &this, mut &this, or this.
type struct Box2Box2.v: i32type struct Box2Box2::put(&this, x: i32) -> i32(parameter) x: i32(parameter) x: i32Box2::put<T>(&this, x: T) -> i32type parameter T(parameter) x: Ttype parameter Tconst NAME: Type = value;At module level, a true compile-time constant, conventionally SCREAMING_SNAKE_CASE. (mut at module level declares mutable global state instead.)
const b: Box2Box2.v: i32const n: i32const b: Box2Box2::put<T>(&this, x: T) -> i32type trait Name : Base {
method(&this, other: &This) -> Ret;
}Names a set of methods a type may implement. Inside the body This is the implementing type; methods may carry default bodies, and a trait may require a base trait (type trait Ord : Eq). Generic code asks for a trait with where T: Name.
type trait BetaBeta::go(&this, u: i32) -> i64(parameter) u: i32type struct IgTnIgTn.v: i32type trait Betatype struct IgTnIgTn::go(&this, u: i32) -> i64IgTn::go<U>(&this, u: U) -> i64type parameter U(parameter) u: Utype parameter Ufunction name<T>(param: Type) -> Ret { ... }Declares a function. Parameters are written name: Type and are never inferred; the return type follows -> and defaults to void. Declaration order does not matter: every signature is collected before any body is checked, so functions may call each other freely and recurse.
function via<T>(x: &T) -> i64
where T: Betatype parameter Twhere T: Beta
(parameter) x: &Twhere T: Ord + Clone, U: HashConstrains generic parameters to types that implement the listed traits, which is what makes those traits' methods callable on T. Bounds on one parameter join with +; parameters separate with ,. A where on a single method narrows the type's parameter for that method only.
(parameter) x: &Tconst ig: IgTnIgTn.v: i32const a: i64const ig: IgTnIgTn::go<U>(&this, u: U) -> i64const d: i64const b: i64for (mut i: i32 = 0; i < n; i++) { ... }
for (item in iterable) { ... }Two forms. The C-style for (init; condition; step) scopes its loop variable to the body. for (x in expr) iterates anything with next() -> Option<T>: an iterator, a range such as 0..n, an Array or Slice (their iter() is inserted for you), or a fixed-size array.
const c: i64function via<T>(x: &T) -> i64
where T: Betatype struct PtPt.v: i32type struct PtPt::step(&this, u: i32) -> i64Pt.v: i32(parameter) u: i32const p: Ptconst fPt::step(&this, u: i32) -> i64const n: i64const fconst p: PtBeta::go(&this) -> i32static Beta::make() -> i32type trait GammaGamma::go(&this) -> i32static Gamma::make() -> i32type struct PP.v: i32type trait Betatype struct PP::go(&this) -> i32static P::make() -> i32type trait Gammaconst p: PP.v: i32const a: i32static P::make() -> i32const b: i32const c: i32const p: Pconst f: () -> i32static i32::max_value() -> i32static i32::min_value() -> i32extern "C" function recv(sockfd: i32, buf: u8*, len: u64, flags: i32) -> i64recv is also declared in std::sys::syscall
std::ffi::libcfunction name(count: i32, args...)A variadic parameter: the trailing bucket receives any number of further arguments with C's calling convention, so the function stays ABI-compatible with the printf family. Wrap the bucket in VaArgs to read typed values from it.
(parameter) w: UTestFormat::Plaintype struct PtPt::step(&this, u: i32) -> i64const f: () -> i32const p: Pmut &this
mut &TypeAn exclusive, mutating borrow. As a receiver, mut &this lets the method modify the fields - and callers can see that from the signature alone. As a parameter type, mut &T lets the callee mutate through the reference while the caller keeps ownership.
const g: (&Wrap<i32>) -> i32const k: (&Wrap<i32>, u16) -> i32const f: (u8, u8) -> u8