@register_passable

Declares that a type should be passed in machine registers.

You can add the @register_passable decorator on a struct to tell Mojo that the type should be passed in machine registers (such as a CPU register; subject to the details of the underlying architecture). For tiny data types like an integer or floating-point number, this is much more efficient than storing values in stack memory. This means the type is always passed by value and cannot be passed by reference.

The basic @register_passable decorator does not change the fundamental behavior of a type: it still needs an __init__() and __copyinit__() method to be copyable (and it may have a __del__() method, if necessary). However, these methods must be declared a little differently for a @register_passable type: The __init__ and __copyinit__ methods are static and must return results by-value instead of using an inout self argument. For example:

@register_passable
struct Pair:
    var a: Int
    var b: Int

    fn __init__(one: Int, two: Int) -> Self:
        return Self{a: one, b: two}

    fn __copyinit__(existing) -> Self:
        return Self{a: existing.a, b: existing.b}

fn test_pair():
    let x = Pair(5, 10)
    var y = x

    print(y.a, y.b)
    y.a = 10
    y.b = 20
    print(y.a, y.b)
test_pair()
5 10
10 20

This behavior is what we expect from Pair, with or without the decorator, but it’s important to notice the signatures for __init__() and __copyinit__() don’t have the usual inout self argument. The compiler won’t allow you to declare them that way; likewise, you can’t use the static signatures above without the @register_passable decorator.

You should be aware of a few other observable effects:

  1. @register_passable types cannot hold instances of types that are not also @register_passable.

  2. @register_passable types do not have a predictable identity, and so the self pointer is not stable/predictable (e.g. in hash tables). This is why the __init__() method above is implicitly static, and must return the constructed instance by value.

  3. @register_passable arguments and result are exposed to C and C++ directly, instead of being passed by-pointer.

  4. @register_passable types cannot have a __moveinit__() constructor, because values passed in a register cannot be passed by reference.

@register_passable("trivial")

Most types that use @register_passable are just “bags of bits,” which we call “trivial” types. These trivial types are simple and should be copied, moved, and destroyed without any custom constructors or a destructor. For these types, you can add the "trivial" argument, and Mojo synthesizes all the lifecycle methods as appropriate for a trivial register-passable type:

@register_passable("trivial")
struct Pair:
    var a: Int
    var b: Int

This is similar to the @value decorator, except when using @register_passable("trivial") the only lifecycle method you’re allowed to define is the __init__() constructor (but you don’t have to)—you cannot define any copy or move constructors or a destructor.

Examples of trivial types include:

  • Arithmetic types such as Int, Bool, Float64 etc.
  • Pointers (the address value is trivial, not the data being pointed to).
  • Arrays of other trivial types, including SIMD.

For more information about lifecycle methods (constructors and destructors) see the section about Value lifecycle.

TODO: This decorator is due for reconsideration. Lack of custom copy/move/destroy logic and “passability in a register” are orthogonal concerns and should be split. This former logic should be subsumed into a more general @value("trivial") decorator, which is orthogonal from @register_passable.