Skip to main content

@register_passable

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). For example:

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

fn __init__(out self, one: Int, two: Int):
self.a = one
self.b = two

fn __copyinit__(out self, existing: Self):
self.a = existing.a
self.b = existing.b

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

print(y.a, y.b)
y.a = 10
y.b = 20
print(y.a, y.b)
@register_passable
struct Pair:
var a: Int
var b: Int

fn __init__(out self, one: Int, two: Int):
self.a = one
self.b = two

fn __copyinit__(out self, existing: Self):
self.a = existing.a
self.b = existing.b

fn test_pair():
var 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()
test_pair()
5 10
10 20
5 10
10 20

This behavior is what we expect from Pair, with or without the 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).

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

  4. @register_passable types are always trivially movable by transferring the register value around without side effects. As such, they may not define an explicit move constructor.

@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 copy or move constructors, or a destructor. For these types, you can add the "trivial" argument, and Mojo synthesizes all the lifecycle methods except for the constructor:

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

A trivial type acts like a register-passable type, with a few extra characteristics:

  • Its fields must also be trivial types.

  • The type is trivially copyable. It conforms to the Copyable trait, and can be copied by copying the bits from one location to another. As such, the type cannot define an explicit copy constructor.

  • The type is trivially movable. It conforms to the Movable trait, and can be moved by moving the bits from one location to another. As such, the type cannot define an explicit move constructor.

  • The type is trivially destructible. It conforms to the AnyType trait and implements a trivial (no-op) destructor. As such, the type cannot define a explicit 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.

Was this page helpful?