11. Operators and special methods

In Mojo some functions have special naming convention, starting and ending with double underscores: "_". Since they start and end with __double underscores_, they are called dunder methods. These methods are treated specially by Mojo compiler. Mojo supports implementation of arithmetic and relational operators, along with special methods that support lifecycle of values.

In previous chapters, we saw many times the arithmetic and relational operators. Most of these operators can be implemented by user defined structs. Mojo provides quite a bit of flexibility in defining your own low level structs and the ability to implement these operators is part of that flexibility. Implementing an operator is as simple as implementing a function.

Many of Mojo’s built in functionality is implemented as libraries. This means that we are able to implement powerful constructs just using basic language features offered by Mojo.

11.1. Arithmetic operators

The following sections describe the various arithmetic operators in Mojo.

11.1.1. Addition

The following are the addition operators.

__add__

The __add__ stands for arithmetic addition "+" between the struct defining the method and self type or another type.

__radd__

The __radd__ method is known as reverse addition, and is used when we try to add two values, where the first value does not have __add__ implemented. In this case, the Mojo compiler checks if the second value has __radd__ implemented, and it calls that one.

__iadd__

The __iadd__ method is called in-place addition and represents arithmetic addition "=". Even if you implement just `\\__add__` and do not implement `\\__iadd__`, the "=" operation would still work as Mojo will just use __add__ as the fallback. However, typically __add__ returns a new instance of the result. In case of large structs (structs with many fields), it could entail a lot of copy operations. The in-place addition can directly change the struct’s internal data, resulting in an efficient execution of the addition method. The __iadd__ therefore does not have a return value as it updates the struct itself.

Since the struct' internal value is mutated, we need to use one of the inout or owned references of self in the method.

The following code listing shows the different operations. Please note that these examples are not meant for production use, as it is intentionally kept incomplete for simplicity’s sake.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __add__(self, other: Self) -> Self:
        print("add invoked")
        return Self(self.val + other.val)

    fn __radd__(self, other: MyFloat) -> Self:
        print("radd invoked")
        return Self(self.val + int(other.val))

    fn __iadd__(inout self, other: Self):
        print("iadd invoked")
        self.val = self.val + other.val

Usage:

    var num: MyInt = MyInt(42)

    var add_res = MyInt(1) + MyInt(2)
    print(add_res.val)

    var radd_res = MyFloat(3.5) + MyInt(2) # Even though MyFloat does not implement __add__ method, we are able to do addition through MyInt's __radd__
    print(radd_res.val)

    var iadd_res = MyInt(10)
    iadd_res += MyInt(20)
    print(iadd_res.val)

11.1.2. Subtraction

The following are the subtraction operators.

__sub__

The __sub__ stands for arithmetic subtraction "-" between the struct defining the method and self type or another type.

__rsub__

The __rsub__ method is known as reverse subtraction, and is used when we try to subtract two values, where the first value does not have __sub__ implemented. In this case, the Mojo compiler checks if the second value has __rsub__ implemented, and it calls that one.

As the name implies, the reverse subtraction swaps the operands. Since subtraction is non-commutative, care must be taken to have correct values as the first operand and second operands. For example, x - y in normal subtraction would be y - x in reverse subtraction.

__isub__

The __isub__ method is called in-place subtraction and represents arithmetic subtraction "-=". The concept of __isub__ is the same as what we saw in __iadd__.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __sub__(self, other: Self) -> Self:
        print("sub invoked")
        return Self(self.val - other.val)

    fn __rsub__(self, other: MyFloat) -> Self:
        print("rsub invoked")
        return Self(int(other.val) - self.val) # Order matters for subtraction; it is not commutative. 

    fn __isub__(inout self, other: Self):
        print("isub invoked")
        self.val = self.val - other.val

Usage:

    var num: MyInt = MyInt(42)

    var sub_res = MyInt(1) - MyInt(2)
    print(sub_res.val)

    var rsub_res = MyFloat(3.5) - MyInt(2) # Even though MyFloat does not implement __sub__ method, we are able to do addition through MyInt's __rsub__
    print(rsub_res.val)

    var isub_res = MyInt(10)
    isub_res -= MyInt(20)
    print(isub_res.val)

11.1.3. Multiplication

The following are multiplication operators.

__mul__

The __mul__ stands for multiplication "*" between the struct defining the method and self type or another type.

__rmul__

The __rmul__ method is known as reverse multiplication, and is used when we try to multiply two values, where the first value does not have __mul__ implemented. In this case, the Mojo compiler checks if the second value has __rmul__ implemented, and it calls that one.

__imul__

The __imul__ method is called in-place multiplication and represents multiplication "*=". The concept of __imul__ is the same as what we saw in __iadd__, except instead of addition, the applied operation is multiplication.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    


    fn __mul__(self, other: Self) -> Self:
        print("mul invoked")
        return Self(self.val * other.val)

    fn __rmul__(self, other: MyFloat) -> Self:
        print("rmul invoked")
        return Self(int(other.val) * self.val) # Will truncate 

    fn __imul__(inout self, other: Self):
        print("imul invoked")
        self.val = self.val * other.val

Usage:

    var num: MyInt = MyInt(42)

    var mul_res = MyInt(3) * MyInt(2)
    print(mul_res.val)

    var rmul_res = MyFloat(3.5) * MyInt(2)
    print(rmul_res.val)

    var imul_res = MyInt(10)
    imul_res *= MyInt(20)
    print(imul_res.val)

11.1.4. Matrix multiplication

The following are operators for matrix multiplication.

__matmul__

The __matmul__ stands for matrix multiplication represented by the symbol "@" between the struct defining the method and self type or another type.

__rmatmul__

The __rmatmul__ stands for the reverse matrix multiplication represented by the symbol "@" between the struct defining the method and self type or another type. Similar to __rsub__, take care to use the appropriate order of the operands when doing the implementation, as the operands are swapped.

__imatmul__

The __imatmul__ stands for in-place matrix multiplication represented by the symbol "@" between the struct defining the method and self type or another type. The concept of __imatmul__ is the same as what we saw in __iadd__, except instead of addition, the applied operation is matrix multiplication.

struct MyFloatMatrix:

    var val: List[List[Float16]]
    var rows: Int
    var cols: Int

    fn __init__(inout self, value: List[List[Float16]], rows: Int, cols: Int):
        self.val = value
        self.rows = rows
        self.cols = cols

    @staticmethod
    fn empty() -> Self:
        return MyFloatMatrix(List[List[Float16]](), 0, 0)

struct MyIntMatrix:

    var val: List[List[Int]]
    var rows: Int
    var cols: Int

    fn __init__(inout self, value: List[List[Int]], rows: Int, cols: Int):
        self.val = value    
        self.rows = rows
        self.cols = cols
    
    @staticmethod
    fn empty() -> Self:
        return MyIntMatrix(List[List[Int]](), 0, 0)

    fn print(self):
        print("....")
        print("Rows:", self.rows, "Cols:", self.cols)
        for row in self.val:
            print()
            for col in row[]:
                print(col[], end=" ")
        print()
        print("----")



    @staticmethod
    fn _matmul_internal(first: Self, second: Self) -> Self:
        if first.cols != first.rows:
            print("Rows and columns do not match. ")
            return MyIntMatrix.empty()
        
        var res: List[List[Int]] = List[List[Int]](capacity=first.rows)

        for i in range(first.rows):
            res.append(List[Int](capacity=second.cols))
            for j in range(second.cols):
                var s = 0
                for k in range(first.cols):
                    s += first.val[i][k] * second.val[k][j]
                res[i].append(s)

        return Self(res, first.rows, second.cols)


    fn __matmul__(self, other: Self) -> Self: # Naive implementation - not for production use
        print("matmul invoked")
        return Self._matmul_internal(self, other)

    fn __rmatmul__(self, other: MyFloatMatrix) -> Self: # Naive implementation - not for production use
        print("rmatmul invoked")
        var res: List[List[Int]] = List[List[Int]](capacity=self.rows)
        for i in range(other.rows):
            res.append(List[Int](capacity=self.cols))
            for j in range(self.cols):
                var s = 0
                for k in range(other.cols):
                    s += int(other.val[i][k]) * self.val[k][j] # Will truncate 
                res[i].append(s)

        return Self(res, other.rows, self.cols)

    fn __imatmul__(inout self, other: Self): # Naive implementation - not for production use
        print("imatmul invoked")
        var res = Self._matmul_internal(self, other)
        self.val = res.val
        self.rows = res.rows
        self.cols = res.cols

Usage:

    var m : List[List[Int]] = List(
            List(1, 2, 1), 
            List(5, 1, 1),
            List(2, 3, 1))
    var n: List[List[Int]] = List(
        List(2, 5),
        List(6, 7),
        List(1, 1))
    
    var flm : List[List[Float16]] = List(
            List[Float16](1.2, 2.3, 1.4), 
            List[Float16](5.2, 1.2, 1.3),
            List[Float16](2.3, 3.4, 1.4))

    var matmul_res = MyIntMatrix(m, 3, 3) @ MyIntMatrix(n, 3, 2)
    matmul_res.print()

    var rmatmul_res = MyFloatMatrix(flm, 3, 3) @ MyIntMatrix(n, 3, 2)
    rmatmul_res.print()

    var imatmul_res = MyIntMatrix(m, 3, 3)
    imatmul_res @= MyIntMatrix(n, 3, 2)
    imatmul_res.print()

11.1.5. Division

The division operators.

__truediv__

The __truediv__ stands for division represented by the symbol "/" between the struct defining the method and self type or another type. The result of \\__truediv is a floating point type with 64 bits.

__rtruediv__

The __rtruediv__ stands for the reverse division represented by the symbol "/" between the struct defining the method and self type or another type. Similar to __rsub__, take care to use the appropriate order of the operands when doing the implementation, as the operands are swapped.

__itruediv__

The __itruediv__ stands for in-place division represented by the symbol "/" between the struct defining the method and self type or another type. The concept of __itruediv__ is the same as what we saw in __iadd__, except instead of addition, the applied operation is division.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    


    fn __truediv__(self, other: Self) -> MyFloat:
        print("truediv invoked")
        return MyFloat(self.val / other.val)

    fn __rtruediv__(self, other: MyFloat) -> MyFloat:
        print("rtruediv invoked")
        return MyFloat(int(other.val) / self.val)

    fn __itruediv__(inout self, other: Self):
        print("itruediv invoked")
        self.val = int(self.val / other.val) # Will truncate 

Usage:

    var num: MyInt = MyInt(42)

    var div_res = MyInt(3) / MyInt(2)
    print(div_res.val)

    var rdiv_res = MyFloat(3.5) / MyInt(2)
    print(rdiv_res.val)

    var idiv_res = MyInt(10)
    idiv_res /= MyInt(20)
    print(idiv_res.val)

Note that __itruediv__ implementation had to convert the result from a Float to Int because MyInt can only store Int as value within it. Mojo has the ability to have a variable with multiple possible types through Variant struct. We will cover that in a later chapter.

__floordiv__

The __floordiv__ stands for floor division (also known as integer division) represented by the symbol "//" between the struct defining the method and self type or another type. As the name suggest, the result of the __floordiv__ is an integer instead of float.

Typically implementations truncate towards zero in case of positive values and away from zero for negative values. For example, 7//3 results in 2, while -7//3 results in -3 and not -2.

__rfloordiv__

The __rfloordiv__ stands for the reverse division represented by the symbol "//" between the struct defining the method and self type or another type. Similar to __rtruediv__, take care to use the appropriate order of the operands when doing the implementation, as the operands are swapped.

__ifloordiv__

The __ifloordiv__ stands for in-place division represented by the symbol "//" between the struct defining the method and self type or another type. The concept of __ifloordiv__ is the same as what we saw in __iadd__, except instead of addition, the applied operation is floor division.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    


    fn __floordiv__(self, other: Self) -> Self:
        print("floordiv invoked")
        return Self(self.val // other.val)

    fn __rfloordiv__(self, other: MyFloat) -> Self:
        print("rfloordiv invoked")
        return Self(int(other.val) // self.val)

    fn __ifloordiv__(inout self, other: Self):
        print("ifloordiv invoked")
        self.val = self.val // other.val  

Usage:

    var num: MyInt = MyInt(42)

    var floordiv_res = MyInt(3) // MyInt(2)
    print(floordiv_res.val)

    var rfloordiv_res = MyFloat(3.5) // MyInt(2)
    print(rfloordiv_res.val)

    var ifloordiv_res = MyInt(10)
    ifloordiv_res //= MyInt(20)
    print(ifloordiv_res.val)

11.1.6. Modulo

The following are the modulo operators.

__mod__

The __mod__ stands for modulo operation represented by the symbol "%" between the struct defining the method and self type or another type. The approach to implement __mod__ is the same as what we saw in __truediv__, except instead of division, the applied operation is modulo operation.

The __mod__ derives its name from the mathematical modulo operation. Modulo operation divides two numbers and returns the remainder of the division.

__rmod__

The __rmod__ stands for the reverse modulo operation represented by the symbol "%" between the struct defining the method and self type or another type. Similar to __rtruediv__, take care to use the appropriate order of the operands when doing the implementation, as the operands are swapped.

__imod__

The __imod__ stands for in-place modulo operation represented by the symbol "%" between the struct defining the method and self type or another type. The concept of __imod__ is the same as what we saw in __itruediv__, except instead of division, the applied operation is modulo.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    


    fn __mod__(self, other: Self) -> Self:
        print("mod invoked")
        return Self(self.val % other.val)

    fn __rmod__(self, other: MyFloat) -> Self:
        print("rmod invoked")
        return Self(int(other.val) % self.val)

    fn __imod__(inout self, other: Self):
        print("imod invoked")
        self.val = self.val % other.val  

Usage:

    var num: MyInt = MyInt(42)

    var mod_res = MyInt(7) % MyInt(2)
    print(mod_res.val)

    var rmod_res = MyFloat(8) % MyInt(2)
    print(rmod_res.val)

    var imod_res = MyInt(37)
    imod_res %= MyInt(20)
    print(imod_res.val)

11.1.7. Exponentiation

The following lists the exponentiation operators.

__pow__

The __pow__ stands for exponential operation represented by the symbol "**" between the struct defining the method and self type or another type. The approach to implement __pow__ is the same as what we saw in __mul__, except instead of multiplication, we apply exponential operation.

__ipow__

The __ipow__ stands for in-place exponential operation represented by the symbol "**" between the struct defining the method and self type or another type. The concept of __ipow__ is the same as what we saw in __imul__, except instead of multiplication, we apply exponential operation.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    


    fn __pow__(self, other: Self) -> Self:
        print("pow invoked")
        return Self(self.val ** other.val)

    fn __ipow__(inout self, other: Self):
        print("ipow invoked")
        self.val **= other.val  

Usage:

    var num: MyInt = MyInt(42)

    var pow_res = MyInt(7) ** MyInt(2)
    print(pow_res.val)

    var ipow_res = MyInt(7)
    ipow_res **= MyInt(2)
    print(ipow_res.val)

11.1.8. Unary operators

The following are the unary operators.

__neg__

The __neg__ stands for the unary operation represented by the symbol "-" for the struct defining the method. The negative sign appears as prefix to the value and typically results in negation of the value.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __neg__(self) -> Self:
        print("neg invoked")
        return Self(-self.val)

Usage:

    var num: MyInt = MyInt(42)

    var neg_res = -MyInt(7)
    print(neg_res.val)
__pos__

The __pos__ stands for the unary operation represented by the symbol "+" for the struct defining the method. The positive sign appears as prefix to the value.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __pos__(self) -> Self:
        print("pos invoked")
        return Self(+self.val)

Usage:

    var num: MyInt = MyInt(42)

    var pos_res = +MyInt(7)
    print(pos_res.val)
__invert__

The __invert__ stands for the unary operation represented by the symbol "~" for the struct defining the method. The invert sign appears as prefix to the value. Typical implementations return bitwise compliment of the value, switching 1 for 0 and vice versa.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __invert__(self) -> Self:
        print("invert invoked")
        return Self(~self.val)

Usage:

    var num: MyInt = MyInt(42)

    var invert_res = ~MyInt(2)
    print(invert_res.val)

11.2. Bitwise operators

The following are the bitwise operators.

11.2.1. Operators

__lshift__

The __lshift__ stands for left shift operation represented by the symbol "<<" between the struct defining the method and self type or another type. Typical implementation shifts the bits of the first operand to the left. The second operand indicates how many bits are to be shifted to the left.

__rlshift__

The __rlshift__ stands for the reverse left shift operation represented by the symbol "<<" between the struct defining the method and self type or another type. Similar to __rtruediv__, take care to use the appropriate order of the operands when doing the implementation, as the operands are swapped.

__ilshift__

The __ilshift__ stands for in-place left shift operation represented by the symbol "<⇐" between the struct defining the method and self type or another type. Typical implementation shifts the bits of the first operand to the left. The second operand indicates how many bits are to be shifted to the left. Instead of returning a new instance like in __lshift__, the __ilshift__ updates its own instance with the result. The concept is similar to __iadd__ mentioned earlier.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __lshift__(self, other: Self) -> Self:
        print("lshift invoked")
        return Self(self.val << other.val)

    fn __rlshift__(self, other: MyFloat) -> Self:
        print("rlshift invoked")
        return Self(int(other.val) << self.val)

    fn __ilshift__(inout self, other: Self):
        print("ilshift invoked")
        self.val <<= other.val

Usage:

    var num: MyInt = MyInt(42)

    var lshift_res = MyInt(2) << MyInt(3)
    print(lshift_res.val)

    var rlshift_res = MyFloat(2) << MyInt(3)
    print(rlshift_res.val)

    var ilshift_res = MyInt(3)
    ilshift_res <<= MyInt(2)
    print(ilshift_res.val)
__rshift__

The __rshift__ stands for right shift operation represented by the symbol ">>" between the struct defining the method and self type or another type. Typical implementation shifts the bits of the first operand to the right. The second operand indicates how many bits are to be shifted to the right.

__rrshift__

The __rrshift__ stands for the reverse right shift operation represented by the symbol ">>" between the struct defining the method and self type or another type. Similar to __rlshift__, take care to use the appropriate order of the operands when doing the implementation, as the operands are swapped.

__irshift__

The __irshift__ stands for in-place right shift operation represented by the symbol ">>=" between the struct defining the method and self type or another type. Typical implementation shifts the bits of the first operand to the left. The second operand indicates how many bits are to be shifted to the left. Instead of returning a new instance like in __rshift__, the __irshift__ updates its own instance with the result. The concept is similar to __iadd__ mentioned earlier.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __rshift__(self, other: Self) -> Self:
        print("rshift invoked")
        return Self(self.val >> other.val)

    fn __rrshift__(self, other: MyFloat) -> Self:
        print("rrshift invoked")
        return Self(int(other.val) >> self.val)

    fn __irshift__(inout self, other: Self):
        print("irshift invoked")
        self.val >>= other.val

Usage:

    var num: MyInt = MyInt(42)

    var rshift_res = MyInt(20) >> MyInt(3)
    print(rshift_res.val)

    var rrshift_res = MyFloat(24) >> MyInt(3)
    print(rrshift_res.val)

    var irshift_res = MyInt(30)
    irshift_res >>= MyInt(2)
    print(irshift_res.val)
__and__

The __and__ stands for bitwise AND operator represented by the symbol "&" between the struct defining the method and self type or another type.

__rand__

The __rand__ stands for reverse bitwise AND operator represented by the symbol "&" between the struct defining the method and self type or another type. This is invoked when the first value does not have __and__ implemented. In this case, the Mojo compiler checks if the second value has __rand__ implemented, and calls that one.

__iand__

The __iand__ stands for in-place bitwise AND operator represented by the symbol "&=" between the struct defining the method and self type or another type. Instead of returning a new instance like in __and__, the __iand__ updates its own instance with the result.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __and__(self, other: Self) -> Self:
        print("and invoked")
        return Self(self.val & other.val)

    fn __rand__(self, other: MyFloat) -> Self:
        print("rand invoked")
        return Self(int(other.val) & self.val)

    fn __iand__(inout self, other: Self):
        print("iand invoked")
        self.val &= other.val

Usage:

    var num: MyInt = MyInt(42)

    var and_res = MyInt(23) & MyInt(6)
    print(and_res.val)

    var rand_res = MyFloat(20) & MyInt(4)
    print(rand_res.val)

    var iand_res = MyInt(10)
    iand_res &= MyInt(2)
    print(iand_res.val)
__or__

The __or__ stands for bitwise OR operator represented by the symbol "|" between the struct defining the method and self type or another type.

__ror__

The __ror__ stands for reverse bitwise OR operator represented by the symbol "|" between the struct defining the method and self type or another type. This is invoked when the first value does not have __or__ implemented. In this case, the Mojo compiler checks if the second value has __ror__ implemented, and calls that one.

__ior__

The __ior__ stands for in-place bitwise OR operator represented by the symbol "|=" between the struct defining the method and self type or another type. Instead of returning a new instance like in __or__, the __ior__ updates its own instance with the result.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __or__(self, other: Self) -> Self:
        print("or invoked")
        return Self(self.val | other.val)

    fn __ror__(self, other: MyFloat) -> Self:
        print("ror invoked")
        return Self(int(other.val) | self.val)

    fn __ior__(inout self, other: Self):
        print("ior invoked")
        self.val |= other.val

Usage:

    var num: MyInt = MyInt(42)

    var or_res = MyInt(5) | MyInt(3)
    print(or_res.val)

    var ror_res = MyFloat(15) | MyInt(17)
    print(ror_res.val)

    var ior_res = MyInt(5)
    ior_res |= MyInt(1)
    print(ior_res.val)
__xor__

The __xor__ stands for bitwise XOR operator represented by the symbol "^" between the struct defining the method and self type or another type.

__rxor__

The __rxor__ stands for reverse bitwise XOR operator represented by the symbol "^" between the struct defining the method and self type or another type. This is invoked when the first value does not have __xor__ implemented. In this case, the Mojo compiler checks if the second value has __rxor__ implemented, and calls that one.

__ixor__

The __ixor__ stands for in-place bitwise OR operator represented by the symbol "^=" between the struct defining the method and self type or another type. Instead of returning a new instance like in __xor__, the __ixor__ updates its own instance with the result.

struct MyFloat:

    var val: Float16

    fn __init__(inout self, value: Float16):
        self.val = value

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __xor__(self, other: Self) -> Self:
        print("xor invoked")
        return Self(self.val ^ other.val)

    fn __rxor__(self, other: MyFloat) -> Self:
        print("rxor invoked")
        return Self(int(other.val) ^ self.val)

    fn __ixor__(inout self, other: Self):
        print("ixor invoked")
        self.val ^= other.val

Usage:

    var num: MyInt = MyInt(42)

    var xor_res = MyInt(5) ^ MyInt(3)
    print(xor_res.val)

    var rxor_res = MyFloat(15) ^ MyInt(17)
    print(rxor_res.val)

    var ixor_res = MyInt(5)
    ixor_res ^= MyInt(3)
    print(ixor_res.val)

11.3. Relational operators

11.3.1. Operators

__eq__

The __eq__ stands for equality operator represented by the symbol "==" between the struct defining the method and self type or another type. The operation returns a Bool value.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __eq__(self, other: Self) -> Bool:
        print("eq invoked")
        return self.val == other.val

Usage:

    var num: MyInt = MyInt(42)

    print(MyInt(5) == MyInt(5))
    print(MyInt(5) == MyInt(3))
__ne__

The __ne__ stands for inequality operator represented by the symbol "!=" between the struct defining the method and self type or another type. The operation returns a Bool value. Note that __ne__ is not invoked when you call not x==y though.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __ne__(self, other: Self) -> Bool:
        print("ne invoked")
        return self.val != other.val

Usage:

    var num: MyInt = MyInt(42)

    print(MyInt(5) != MyInt(5))
    print(MyInt(5) != MyInt(3))
    print(not MyInt(5) == MyInt(3))
__lt__

The __lt__ stands for less-than operator represented by the symbol "<" between the struct defining the method and self type or another type. The operation returns a Bool value.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __lt__(self, other: Self) -> Bool:
        print("lt invoked")
        return self.val < other.val

Usage:

    var num: MyInt = MyInt(42)

    print(MyInt(5) < MyInt(5))
    print(MyInt(3) < MyInt(5))
__gt__

The __gt__ stands for greater-than operator represented by the symbol ">" between the struct defining the method and self type or another type. The operation returns a Bool value.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __gt__(self, other: Self) -> Bool:
        print("gt invoked")
        return self.val > other.val

Usage:

    var num: MyInt = MyInt(42)

    print(MyInt(5) > MyInt(5))
    print(MyInt(5) > MyInt(3))
__le__

The __le__ stands for less-than-or-equal-to operator represented by the symbol "⇐" between the struct defining the method and self type or another type. The operation returns a Bool value.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __le__(self, other: Self) -> Bool:
        print("le invoked")
        return self.val <= other.val

Usage:

    var num: MyInt = MyInt(42)

    print(MyInt(5) <= MyInt(5))
    print(MyInt(5) <= MyInt(3))
__ge__

The __ge__ stands for greater-than-or-equal-to operator represented by the symbol ">=" between the struct defining the method and self type or another type. The operation returns a Bool value.

struct MyInt:

    var val: Int

    fn __init__(inout self, value: Int):
        self.val = value    

    fn __ge__(self, other: Self) -> Bool:
        print("ge invoked")
        return self.val >= other.val

Usage:

    var num: MyInt = MyInt(42)

    print(MyInt(5) >= MyInt(5))
    print(MyInt(3) >= MyInt(5))

11.4. Index operators

Any large scale program would use collection data types such as lists, arrays, dictionaries extensively. Some programming languages have built-in syntax to make usage of such types convenient. Mojo has built-in syntactical support for collection types. Mojo allows accessing collection like data types using the syntax []. For example, list[2]. It also allows setting of values at a given index (e.g. list[2]=5). In line with Mojo’s philosophy of moving as much functionality as possible to libraries, instead of having specially treated data types that have exclusive privilege of syntax, Mojo opened up the index operator capability to any type that defines __getitem__ and __setitem__ methods. This means that your custom List struct will have the same syntactical support like the List bundled with Mojo.

11.4.1. Operators

__getitem__

The __getitem__ is the method invoked when you try to access values stored within the collection using the my_list[index] syntax (where my_list is the collection and index is the position of the element desired from the list).

__setitem__

The __setitem__ is the method invoked when you try to assign values to a collection using the my_list[index]=value syntax (where my_list is the collection and index is the position at which the value will be assigned).

struct MyCollection:
    var list: List[String]

    fn __init__(inout self, list: List[String]):
        self.list = list

    fn __getitem__(self, x: Int) -> String:
        return self.list[x] 

    fn __setitem__(inout self, x: Int, val: String):
        self.list[x] = val

Usage:

    var x = MyCollection(List(String("A"), String("B")))
    print("Before:", x[1]) # Gets
    x[1] = String("C") # Sets
    print("After:", x[1])

It is also possible to get and set using multiple indices, especially useful for Matrix like data structures. This means that we can use in our code my_list[row, col, and so on…​].

The following code listing shows such an example for a matrix. Note that this example is not a production quality implementation.

struct MyMatrix:

    var val: List[List[String]]
    var rows: Int
    var cols: Int

    fn __init__(inout self, rows: Int, cols: Int):
        self.rows = rows
        self.cols = cols
        self.val = List[List[String]](capacity=rows)
        for row in range(rows):
            self.val.append(List[String](capacity=cols))
            for col in range(cols):
                self.val[row].append(String("None"))
    
    fn __getitem__(self, row: Int, col: Int) -> String:
        return self.val[row][col]
    
    fn __setitem__(inout self, row: Int, col: Int, s: String):
        self.val[row][col] = s

Usage:

    var y = MyMatrix(5, 3)
    print("Before:", y[1, 2]) # Gets
    y[1, 2] = String("D") # Sets
    print("After:", y[1, 2])
__getitem__ with Slice

Mojo supports getting a slice of a collection or a container using the slicing operator.

The following diagram shows the structure of the slicing operator.

Slice

In order to be able to get a slice from a collection or a container, the collection or the container must implement method __getitem__ that takes a Slice object. This Slice object is instantiated by the Mojo compiler when you use the slice operator. In a way you can think of the [start:stop:step] as a literal form instantiating a Slice.

The start argument of Slice is the start index of the collection from where the slice will be taken. The end argument is the last index until which the slice will be taken (the element referred by this index will be excluded, only its previous element will be taken as last for the slice). The step gives the number of increments to be taken to get the next element.

Any of the arguments of the Slice can be negative. A negative value means reversal of the indexing or stepping. Care must be taken to provide valid combinations though, otherwise it results in error.

struct MySliceableCollection:
    var list: List[String]

    fn __init__(inout self, list: List[String]):
        self.list = list

    fn __getitem__(self, slc: Slice) -> List[String]:
        return self.list[slc.start:slc.end:slc.step]

Usage:

    var z = MySliceableCollection(
        List(String("H"), 
        String("E"), 
        String("L"),
        String("L"),
        String("O"),
        String("W"),
        String("O"),
        ))
    for i in z[0:3:1]: 
        print(i[], end=" ")
    print()
    for i in z[1:6:2]: 
        print(i[], end=" ")
    print()
    for i in z[1:]: 
        print(i[], end=" ")
    print()
    for i in z[:1]: 
        print(i[], end=" ")
    print()
    for i in z[-3:]: 
        print(i[], end=" ")
    print()
    for i in z[:-3]: 
        print(i[], end=" ")
    print()
    for i in z[::-1]: 
        print(i[], end=" ")
    print()

11.5. Context management methods

In large programs, we often need to resources like files and database connections. When we open access to those resources, we typically have a handle, which we use to perform actions. However, once we have done with our actions, we must remember to cleanup or close the resources, otherwise we end up with dangling resources, memory leaks, locked files, etc. Mojo provides with keyword for managing such context or resources.

The following diagram illustrates the syntax of the with statement.

With

11.5.1. Methods

__enter__

The method that handles the allocation of the resource and returns a resource. The resource has scope only within the body of the with. It is not mandatory to assign the return value to a variable, especially if it is not being used.

__exit__

The method that handles the cleanup of the resource. There are two implementations of the __exit__ method, one without any arguments __exit__(self, Error) and one with Error as an argument __exit__(self, Error). The __exit__(self, Error) is invoked when the with body has an exception and exits the with block abnormally. The method __exit__(self, Error) returns a Bool to indicate whether or not to propagate the error further.

Any resources allocated in the __enter__ must be cleaned up at both the __exit__ methods, otherwise we would end up with dangling resources. For example, if __exit__(self, Error) is not properly implemented, resource leaks will occur only when there are exceptions raised within the with body. This will lead to rare but difficult to find defects.

The following code listing shows an example for the context manager.

struct Resource:

    var name: String

    fn __init__(inout self, name: String):
        self.name = name

    fn open(self): 
        print("Opened")

    fn close(self): 
        print("Close")

    fn __copyinit__(inout self, other:Resource): 
        self.name = other.name

struct MyResourceManager:

    var resource: Resource
    fn __init__(inout self): 
        self.resource = Resource("a_resource")

    fn __enter__(self) -> Resource:
        print("Entered context")
        self.resource.open()
        return self.resource
    
    fn __exit__(self):
        self.resource.close()
        print("Exited context")

    fn __exit__(self, err: Error) -> Bool:
        self.resource.close()
        print("Exited context")
        return False

Usage:

    with MyResourceManager() as res:
        print("Inside context, resource is:", res.name)
        raise Error("An error while processing")

11.6. Other special methods

11.6.1. Methods

__len__

The __len__ is defined within Sized trait and is used by the built-in len function. The __len__ method returns the length or size of the struct implementing it.

__int__

The __int__ is defined within Intable trait and is used by the built-in int function. The __int__ method returns an integer representation of the struct implementing it.

__bool__

The __bool__ is defined within Boolable trait and is used by conditional statements such as if to convert the given value to a boolean value for evaluation. The __bool__ method returns a boolean representation of the struct implementing it.

__str__

The __str__ is defined within Stringable trait and is used by the built-in str function. The __str__ method returns an string representation of the struct implementing it. The built-in function print uses __str__ before it prints the given value.

The following code listing provides examples of special methods.

struct MyStruct(Sized, Intable, Boolable, Stringable):

    var ints: List[Int]

    fn __init__(inout self, ints: List[Int]):
        self.ints = ints
    
    fn __len__(self) -> Int:
        print("len called")
        return len(self.ints)
    
    fn __int__(self) -> Int:
        print("int called")
        var sum: Int = 0
        for i in range(len(self.ints)):
            sum += self.ints[i]
        return sum
    
    fn __bool__(self) -> Bool:
        print("bool called")
        return len(self.ints)>0
    
    fn __str__(self) -> String:
        print("str called")
        return "MyStruct"

Usage:

    var st = MyStruct(List(1, 2, 3))
    print(len(st))
    print(int(st))
    if st: # Uses __bool__
        print("MyStruct is true")
    print(str(st))
    print(st) # Can directly print st because the __str__ method is implemented
__is__

The __is__ method is used by the is clause to compare the identity between two values, and returns True if the identities are the same. Note that it is different from the eq method. The eq compares if two values are the same content-wise, while is checks if the two values are having the same identities. This means that two objects may have exactly same content, but different identities. Typical implementations check if the memory location of the two values are the same, in which case it would be considered as being identical.

__isnot__

The __isnot__ method is used by the is not clause to compare the identity between two values, and returns True if the identities are not the same. It is the opposite of the is clause, and in most cases it is sufficient for the implementation to return a negation of the is method.

The following code listing provides examples of is and isnot methods.

struct MyStruct:

    var ptr: UnsafePointer[Int]

    fn __init__(inout self, ptr: UnsafePointer[Int]): 
        self.ptr = ptr

    fn __is__(self, other: MyStruct) -> Bool:
        print("__is__ called")
        return self.ptr == other.ptr

    fn __isnot__(self, other: MyStruct) -> Bool:
        print("__isnot__ called")
        return not(self is other)

Usage:

    var x: Int = 10
    var y: Int = 10
    print(MyStruct(UnsafePointer.address_of(x)) is MyStruct(UnsafePointer.address_of(y))) # Results in False
    print(MyStruct(UnsafePointer.address_of(x)) is MyStruct(UnsafePointer.address_of(x))) # Results in True
    print(MyStruct(UnsafePointer.address_of(x)) is not MyStruct(UnsafePointer.address_of(y))) # Results in True
__getattr__

Many dynamic languages allow us to dynamically define attributes that do not exist in the original struct definition (or class definition in most languages). This ability allows programmers to define an ergonomic API, especially for object relational mapping. Even though Mojo is a statically compiled language, it allows for such a dynamic definition of attributes through a combination of methods, __getattr__ and __setattr__.

The __getattr__ method takes in the attribute name as an argument. When you call an attribute my_attr within a struct (my_struct.my_attr), it is this attribute name my_attr that gets passed as the argument to the method __getattr__. Within the __getattr__, you can as an example return a result based on a database query involving the attribute name. Mojo does not restrict what you do with the attribute name, as long as you return a value conforming to the return type of the function.

__setattr__

The __setattr__ method takes in the attribute name and its value as arguments. When you set an attribute my_attr within a struct with a value (my_struct.my_attr = 'a value'), the __setattr__ is called with the given attribute name my_attr and the value 'a value' passed as arguments.

The following code listing provides examples of getattr and setattr methods.

struct MyStruct:

    var fields: Dict[String, String]

    fn __init__(inout self, fields: Dict[String, String]):
        self.fields = fields
    
    fn __getattr__(self, attr: String) raises -> String:
        return self.fields[attr]

    fn __setattr__(inout self, attr: String, value: String) raises:
        self.fields[attr] = value

Usage:

    var d: Dict[String, String] = Dict[String, String]()
    d["name"] = "IK"
    var st = MyStruct(d)
    print(st.name) # __getattr__ is called here
    st.name = "PK" # __setattr__ is called here
    print(st.name)
__contains__

In Mojo you can check if a value is contained within a struct using the in operator. For example, "IK" in my_string, where my_string is a string and the expression results in value True if the literal IK is found within my_string.

The __contains__ method is used by the in operator to check if a given value is within the struct defining that method.

The following code listing provides an example of contains method.

struct MyStruct:

    var ints: List[Int]

    fn __init__(inout self, ints: List[Int]):
        self.ints = ints
    
    fn __contains__(self, value: Int) -> Bool:
        for i in self.ints:
            if i[] == value:
                return True
        return False

Usage:

    var my_struct = MyStruct(List(1, 2, 3))
    print(1 in my_struct) # Returns True
    print(5 in my_struct) # Returns False

The __contains__ method also works with custom types as shown in the example below.

struct MyStruct2:

    var first_name: String
    var last_name: String

    fn __init__(inout self, first_name: String, last_name: String):
        self.first_name = first_name
        self.last_name = last_name
    
    fn __contains__(self, other: MyStruct2) -> Bool:
        return (self.first_name == other.first_name) or self.last_name == other.last_name

Usage:

    var my_struct2 = MyStruct2("Ram", "C")
    print(MyStruct2("Ram", "T") in my_struct2) # Returns True
    print(MyStruct2("Kri", "C") in my_struct2) # Returns True
    print(MyStruct2("C", "Ram") in my_struct2) # Returns False