Skip to content
CryoCryo home
Stdlibcore

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..b is inclusive. In expression position it is half-open. See Pattern Matching.

Properties

PropertyType
startT
endT

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

PropertyType
startT
endT
exhaustedboolean

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;

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.

TraitOperator
Add Sub Mul Div Rem+ - * / %
Negunary -
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;