ops
import std::core::ops; — in the prelude · source
Types
Range<T>
type struct Range<T> {
start: T;
end: T;
static new(start: T, end: T) -> Range<T>;
}
a..b desugars to Range::new(a, b) and is half-open: it yields start, then every successor below end.
for (i in 0..5) { println(f"{i}"); } // 0 1 2 3 4
In pattern position
a..bis inclusive. In expression position it is half-open. See Pattern Matching.
Properties
| Property | Type |
|---|---|
start | T |
end | T |
Constructors
static new(start: T, end: T) -> Range<T>;
RangeInclusive<T>
type struct RangeInclusive<T> {
start: T;
end: T;
exhausted: boolean;
static new(start: T, end: T) -> RangeInclusive<T>;
}
The closed form, a..=b. The exhausted flag is what lets it yield end itself and then stop, without needing a value past end to exist.
Properties
| Property | Type |
|---|---|
start | T |
end | T |
exhausted | boolean |
Constructors
static new(start: T, end: T) -> RangeInclusive<T>;
Trait implementations
Range
implement<T> trait Iterator<T> for struct Range<T> where T: Step
RangeInclusive
implement<T> trait Iterator<T> for struct RangeInclusive<T> where T: Step
Step
type trait Step : Ord {
successor(&this) -> This;
}
Step is what makes one generic Range<T> work across widths — it supplies successor, and it requires Ord because bounded iteration has to know when it has passed end. It is implemented for every fixed-width integer up to 64 bits, and for char.
Methods
successor(&this) -> This;
successor(&this) -> This;The next value after this. Wraps on overflow at the type's maximum; Range stops before reaching that case because the bound check uses Ord.
Implementors
implement trait Step for i8
implement trait Step for i16
implement trait Step for i32
implement trait Step for i64
implement trait Step for u8
implement trait Step for u16
implement trait Step for u32
implement trait Step for u64
implement trait Step for char
Operator traits
type trait Add<Rhs, Output> {
add(&this, rhs: &Rhs) -> Output;
}
type trait Sub<Rhs, Output> {
sub(&this, rhs: &Rhs) -> Output;
}
type trait Mul<Rhs, Output> {
mul(&this, rhs: &Rhs) -> Output;
}
type trait Div<Rhs, Output> {
div(&this, rhs: &Rhs) -> Output;
}
type trait Rem<Rhs, Output> {
rem(&this, rhs: &Rhs) -> Output;
}
type trait Neg<Output> {
neg(&this) -> Output;
}
type trait BitAnd<Rhs, Output> {
bitand(&this, rhs: &Rhs) -> Output;
}
type trait BitOr<Rhs, Output> {
bitor(&this, rhs: &Rhs) -> Output;
}
type trait BitXor<Rhs, Output> {
bitxor(&this, rhs: &Rhs) -> Output;
}
type trait Shl<Rhs, Output> {
shl(&this, rhs: &Rhs) -> Output;
}
type trait Shr<Rhs, Output> {
shr(&this, rhs: &Rhs) -> Output;
}
type trait Not<Output> {
not(&this) -> Output;
}
type trait BitNot<Output> {
bitnot(&this) -> Output;
}
type trait Index<Idx, Output> {
index(&this, idx: Idx) -> Output*;
}
type trait Deref<Target> {
deref(&this) -> Target*;
}
Implementing one of these lets the compiler rewrite the corresponding operator on your type. The rewrite is type-directed and left-hand-driven — a OP b dispatches on a's type — and it only fires when the primitive rules do not apply, so 1 + 2 and pointer arithmetic stay on the native path. Primitives deliberately do not implement these traits.
| Trait | Operator |
|---|---|
Add Sub Mul Div Rem | + - * / % |
Neg | unary - |
BitAnd BitOr BitXor Shl Shr | & | ^ << >> |
Not / BitNot | ! / ~ |
Index<Idx, Output> | a[i] |
Deref<Target> | *b, and auto-deref on b.field / b.method() |
The right operand is taken by reference so an owned aggregate is not moved, and Rhs/Output are separate type parameters so a type can (say) add a scalar to a vector and yield a vector. Compound assignment routes through the same trait: a += b evaluates a.add(&b) and stores the result back.
Index and Deref both return a pointer, which makes the single impl serve reads, writes, and compound assignment alike — v = a[i], a[i] = v, and a[i] += v all go through index. There is no separate DerefMut, because Cryo pointers are not const-qualified. The one Deref impl in the library is Box<T>.
Allocate
type trait Allocate {
static allocate(size: u64, align: u64) -> void*;
static release(ptr: void*, align: u64) -> void;
}
new and delete are sugar over Allocate, as a[i] is over Index and *p over Deref. new T { ... } of a struct that implements it takes its storage from T::allocate(size, align), then stores the literal into it as for any new, and new T[n] of such an element type takes the block from the same allocate; delete p runs the pointee's Drop and hands the storage to release. A type with no implementation uses the global allocator.
release does nothing unless an implementation writes it, which is what an arena wants: its storage is reclaimed whole, never one object at a time. Only a struct may implement it: new of an enum or a primitive never calls an implementation, so one is refused (E0312). A generic struct's implementation serves each instance's new and delete. The full rules are in Heap Allocation.
Static functions
static allocate(size: u64, align: u64) -> void*;
static release(ptr: void*, align: u64) -> void;
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 Range<T> {
start: T;
end: T;
}type parameter TRange.start: Ttype parameter TRange.end: Tstatic 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 Range::new(start: T, end: T) -> Range<T>(parameter) start: T(parameter) end: Ttype struct Range<T> {
start: T;
end: T;
}for (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.
ifor (item in iterable) { ... }Separates the loop variable from the sequence in a for loop. The sequence is evaluated once; the loop lowers to loop { match (it.next()) { Some(x) => .., None => break } }.
start..endA half-open range - start up to but excluding end - and sugar for Range::new(start, end): an iterator usable in for, and an ordinary value anywhere else. It binds looser than arithmetic, so a..b + 1 is a..(b + 1).
type struct RangeInclusive<T> {
start: T;
end: T;
exhausted: boolean;
}RangeInclusive.start: Ttype parameter TRangeInclusive.end: TRangeInclusive.exhausted: booleanstatic RangeInclusive::new(start: T, end: T) -> RangeInclusive<T>type struct RangeInclusive<T> {
start: T;
end: T;
exhausted: boolean;
}type 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 Step : Ord {
successor(&this) -> This;
}type trait Ord : Eq {
compare(&this, other: &This) -> Ordering;
}Types that have a total order: every pair is Less, Equal, or Greater. compare must be consistent with equals: if a.equals(b) then a.compare(b) == Equal.
Step::successor(&this) -> ThisThe next value after this. Wraps on overflow at the type's maximum; Range stops before reaching that case because the bound check uses Ord.
&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.
ThisThe implementing type, inside a trait or implement body. other: &This in a trait method is a reference to whichever type implements it; This::Item names an associated type.
type trait Add<Rhs, Output> {
add(&this, rhs: &Rhs) -> Output;
}Arithmetic operator traits. Implementing one lets the compiler rewrite the corresponding operator on a user type (or a generic param bounded by the trait) into the trait method: a + b => a.add(&b), and likewise - => sub, * => mul, / => div, % => rem.
type parameter Rhstype parameter OutputAdd::add(&this, rhs: &Rhs) -> Output(parameter) rhs: &Rhstype parameter Rhstype parameter Outputtype trait Sub<Rhs, Output> {
sub(&this, rhs: &Rhs) -> Output;
}Sub::sub(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait Mul<Rhs, Output> {
mul(&this, rhs: &Rhs) -> Output;
}Mul::mul(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait Div<Rhs, Output> {
div(&this, rhs: &Rhs) -> Output;
}Div::div(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait Rem<Rhs, Output> {
rem(&this, rhs: &Rhs) -> Output;
}Rem::rem(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait Neg<Output> {
neg(&this) -> Output;
}Unary negation: -x => x.neg(). Same type-directed discipline as the binary arithmetic traits; numeric negation stays on the native path.
Neg::neg(&this) -> Outputtype parameter Outputtype trait BitAnd<Rhs, Output> {
bitand(&this, rhs: &Rhs) -> Output;
}Bitwise and shift operator traits, rewritten exactly like the arithmetic ones: a & b => a.bitand(&b), and likewise | => bitor, ^ => bitxor, << => shl, >> => shr.
BitAnd::bitand(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait BitOr<Rhs, Output> {
bitor(&this, rhs: &Rhs) -> Output;
}BitOr::bitor(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait BitXor<Rhs, Output> {
bitxor(&this, rhs: &Rhs) -> Output;
}BitXor::bitxor(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait Shl<Rhs, Output> {
shl(&this, rhs: &Rhs) -> Output;
}Shl::shl(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait Shr<Rhs, Output> {
shr(&this, rhs: &Rhs) -> Output;
}Shr::shr(&this, rhs: &Rhs) -> Outputtype parameter Rhstype parameter Outputtype trait Not<Output> {
not(&this) -> Output;
}Unary complement traits. !x => x.not() (logical, for a user "boolean-like" type) and ~x => x.bitnot() (bitwise). Same type-directed discipline as Neg; the primitive bool/integer forms stay on the native path.
Not::not(&this) -> Outputtype parameter Outputtype trait BitNot<Output> {
bitnot(&this) -> Output;
}BitNot::bitnot(&this) -> Outputtype parameter Outputtype trait Index<Idx, Output> {
index(&this, idx: Idx) -> Output*;
}Index<Idx, Output> is the contract for container[idx]-style access, returning a pointer to the element. The compiler rewrites a[i] into *(a.index(i)); because the deref of the returned Output* is a place, the one impl serves reads (v = a[i]), writes (a[i] = v), and compound assignment (a[i] += v).
type parameter IdxIndex::index(&this, idx: Idx) -> Output*(parameter) idx: Idxtype parameter Idxtype parameter Outputtype trait Deref<Target> {
deref(&this) -> Target*;
}Deref<Target> exposes the value a smart pointer points at. The returned Target* is mutable (Cryo pointers are not const-qualified), so this one trait covers both shared and mutable access - there is no separate DerefMut.
type parameter TargetDeref::deref(&this) -> Target*type parameter Targettype trait Allocate {
static allocate(size: u64, align: u64) -> void*;
static release(ptr: void*, align: u64) -> void;
}Allocate is where new gets its memory and delete returns it. new T { .. } of a struct that implements it calls T::allocate(size, align) for the storage, then stores the literal as for any other new; delete p runs the pointee's Drop, then hands the storage to release(p, align).
static Allocate::allocate(size: u64, align: u64) -> void*(parameter) size: u64(parameter) align: u64static Allocate::release(ptr: void*, align: u64) -> void(parameter) ptr: void*import a::b;
import a::b::{ X, Y };
import a::b as c;
import a::b::*;Brings a module, or selected items from it, into scope. One path per declaration: use the brace form for several items, as to alias, and * for everything public (prefer the brace form; wildcards invite name collisions).
static Range::new(start: T, end: T) -> Range<T>Range.start: TRange.end: Tstart..=endAn inclusive range - start through end - and sugar for RangeInclusive::new(start, end). Also a match pattern: 1..=5 => { ... }.
RangeInclusive.exhausted: booleanimplement 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.
type parameter Twhere T: Step
type trait Iterator {
type Item;
next(mut &this) -> Option<This::Item>;
count(mut &this) -> u64;
fold<Acc>(mut &this, initial: Acc, f: (Acc, This::Item) -> Acc) -> Acc;
for_each(mut &this, f: (This::Item) -> void) -> void;
take(this, n: u64) -> TakeIter<This>;
skip(this, n: u64) -> SkipIter<This>;
map<B>(this, f: (This::Item) -> B) -> MapIter<This, B>;
filter(this, pred: (&This::Item) -> boolean) -> FilterIter<This>;
chain<J>(this, other: J) -> ChainIter<This, J>;
// … 7 more
}implement trait Trait for Type { ... }Names the type a trait implementation is for. The block that follows supplies the bodies the trait requires.
language referencewhere 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.
type trait Step : Ord {
successor(&this) -> This;
}Step::successor(&this) -> ThisThe next value after this. Wraps on overflow at the type's maximum; Range stops before reaching that case because the bound check uses Ord.
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 Add<Rhs, Output> {
add(&this, rhs: &Rhs) -> Output;
}Arithmetic operator traits. Implementing one lets the compiler rewrite the corresponding operator on a user type (or a generic param bounded by the trait) into the trait method: a + b => a.add(&b), and likewise - => sub, * => mul, / => div, % => rem.
type trait Sub<Rhs, Output> {
sub(&this, rhs: &Rhs) -> Output;
}type trait Mul<Rhs, Output> {
mul(&this, rhs: &Rhs) -> Output;
}type trait Div<Rhs, Output> {
div(&this, rhs: &Rhs) -> Output;
}type trait Rem<Rhs, Output> {
rem(&this, rhs: &Rhs) -> Output;
}type trait Neg<Output> {
neg(&this) -> Output;
}Unary negation: -x => x.neg(). Same type-directed discipline as the binary arithmetic traits; numeric negation stays on the native path.
type trait BitAnd<Rhs, Output> {
bitand(&this, rhs: &Rhs) -> Output;
}Bitwise and shift operator traits, rewritten exactly like the arithmetic ones: a & b => a.bitand(&b), and likewise | => bitor, ^ => bitxor, << => shl, >> => shr.
type trait BitOr<Rhs, Output> {
bitor(&this, rhs: &Rhs) -> Output;
}type trait BitXor<Rhs, Output> {
bitxor(&this, rhs: &Rhs) -> Output;
}type trait Shl<Rhs, Output> {
shl(&this, rhs: &Rhs) -> Output;
}type trait Shr<Rhs, Output> {
shr(&this, rhs: &Rhs) -> Output;
}type trait Not<Output> {
not(&this) -> Output;
}Unary complement traits. !x => x.not() (logical, for a user "boolean-like" type) and ~x => x.bitnot() (bitwise). Same type-directed discipline as Neg; the primitive bool/integer forms stay on the native path.
type trait BitNot<Output> {
bitnot(&this) -> Output;
}type trait Index<Idx, Output> {
index(&this, idx: Idx) -> Output*;
}Index<Idx, Output> is the contract for container[idx]-style access, returning a pointer to the element. The compiler rewrites a[i] into *(a.index(i)); because the deref of the returned Output* is a place, the one impl serves reads (v = a[i]), writes (a[i] = v), and compound assignment (a[i] += v).
type struct Output {
status: ExitStatus;
stdout: Array<u8>;
stderr: Array<u8>;
}type trait Deref<Target> {
deref(&this) -> Target*;
}Deref<Target> exposes the value a smart pointer points at. The returned Target* is mutable (Cryo pointers are not const-qualified), so this one trait covers both shared and mutable access - there is no separate DerefMut.
Index::index(&this, idx: Idx) -> Output*new T { field: value }
new E::Variant(args)
new T[n]Heap allocation. new T { .. } places a struct literal on the heap and yields a T*, and new E::V(args) does the same for an enum variant; new T[n] reserves n uninitialised T - the typed malloc. The storage comes from T's Allocate implementation, or else the global allocator. Parentheses after a struct's name are refused (E0117): there are no constructors, and a struct's static new(..) is an ordinary method.
delete p;Releases what new T { .. } allocated: the pointee's Drop and the values its fields own, then its storage, through T's Allocate::release. Memory from new T[n] or malloc is released with free instead.
type trait Allocate {
static allocate(size: u64, align: u64) -> void*;
static release(ptr: void*, align: u64) -> void;
}Allocate is where new gets its memory and delete returns it. new T { .. } of a struct that implements it calls T::allocate(size, align) for the storage, then stores the literal as for any other new; delete p runs the pointee's Drop, then hands the storage to release(p, align).
function 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.
static Allocate::allocate(size: u64, align: u64) -> void*static Allocate::release(ptr: void*, align: u64) -> void