Operators

Operators overview

These following operator groups are listed from the highest priority group to the lowest priority group.

Primary operators

Unary operators

Multiplicative operators

Additive operators

Shift operators

Spaceship operator

Comparison operators

Logical AND operator

Logical XOR operator

Logical OR operator

Equality operator

The strict equality operators can be used to check reference equality, skipping any equality operator overloads. For value types such as structs or tuples, the strict equality operator will perform a memberwise strict equality check.

Conditional AND operator

Conditional OR operator

Null-coalescing operator

Conditional operator

Assignment operators

Assignments result in the new value of x.

Type attribute operators

Ref operators

Params operator

See Variable argument counts for examples.

Range operators

See Range expression for examples.

Index from end operator

Index is mostly used for indexing collections and constructing ranges.

let list = scope List<int>() { 5, 1, 0 };

let first = list[0];
// first == 5

// Indexing from the back starts at Count (3 in this case), which is out of range. Thus we get the last element by counting down one -> ^1
let last = list[^1];
// last == 0

Casting

The (T)x cast operator can directly perform many kinds of type conversions, but there are some special cases: * Unboxing. (T)obj where obj is an Object and T is a valuetype will perform an boxing. This unboxing can fail at runtime in Debug mode (when Dynamic Cast Checks are enabled). You can use an obj is T check or a obj as T? expression to safely unbox. * Retrieving an object’s address: the expression (void*)obj where obj is an Object type is actually an unboxing request, not a type conversion. System.Internal.UnsafeCastToPtr can return the address of an Object as a void*. * Casting to an unrelated type. Sometimes double-casts can be used to achieve what would otherwise be an illegal cast. For example, with (void*)handle where handle is a the typed primitive struct Handle : int, the cast directly to void* is not allowed, but (void*)(int)handle is allowed.

Operator overloading

Structs and classes can provide operator overloads. Comparison operator selection is flexible, in that not all combination of <, <=, ==, !=, >, and >= need to be defined. The “inverse” of operators will be called if available, or if just the <=> operator is defined then that can be used for all comparison types as well.

struct Vector2
{
    float x;
    float y;

    public this(float x, float y)
    {
        this.x = x;
        this.y = y;
    }

    /* Binary + operator */
    public static Vector2 operator+(Vector2 lhs, Vector2 rhs)
    {
        return .(lhs.x + rhs.x, lhs.y + rhs.y);
    }

    /* Unary '-' operator */
    public static Vector2 operator-(Vector2 val)
    {
        return .(-val.x, -val.y);
    }

    /* Unary '++' operator */
    public static Vector2 operator++(Vector2 val)
    {
        return .(val.x + 1, val.y + 1);
    }

    /* Non-static unary '--' operator */
    public void operator--() mut
    {
        x--;
        y--;
    }

    /* Assignment '+=' operator */
    public void operator+=(Vector2 rhs) mut
    {
        x += rhs.x;
        y += rhs.y;
    }

    /* Comparison operator */
    public static int operator<=>(Vector2 lhs, Vector2 rhs)
    {
        /* Compare on X, or on Y if X's are equal */
        int cmp = lhs.x <=> rhs.x;
        if (cmp != 0)
            return cmp;
        return lhs.y <=> rhs.y;
    }

    /* Conversion operator from float[2] */
    public static operator Vector2(float[2] val)
    {
        return .(val[0], val[1]);
    }
}

Binary operators can be marked with the [Commutable] attribute, which allows for certain operator transformations. A commutable “A < B” operator, for example, can be used for “B > A”, “!(A >= B)”, and !(B <= A). A commutable “A == B” operator can be used for “B == A”, “!(A != B)”, and “!(B != A)”.