@register_passable
You can decorate a type with @register_passable
which allows a type to passed through registers and adds some generic behaviour, for example a UInt32
is just 32 bits for the actual value and can be directly copied into and out of registers, while a String
contains a pointer that requires special constructor and destructor behavior to allocate and free memory so it's memory only
.
Create a type with a pair of UInt32
and mark it register passable:
@register_passable
struct Pair:
var a: UInt32
var b: UInt32
fn __init__(a: UInt32, b: UInt32) -> Self:
return Self{a: 2, b: 4}
fn __copyinit__(self) -> Self:
return Self{a: 2, b: 4}
fn __del__(owned self):
print("running __del__")
__init__
, __copyinit__
and __del__
aren't required, this is just to indicate what you can define on a @register_passable
type, for example printing something when the object is dropped:
fn test():
let x = Pair(5, 10)
var y = x
y.a = 10
y.b = 20
print(x.a, x.b)
print(y.a, y.b)
test()
running __del__
2 4
running __del__
10 20
Generally you will also want to mark it with the @value decorator, which implements all the boilerplate for you:
@value
@register_passable
struct Pair:
var a: Int
var b: Int
let x = Pair(5, 10)
print(x.a, x.b)
5 10
Trying to define __moveinit__
will result in an error, the whole idea behind @register_passable
is that you can copy it into or out of a register by copying:
@register_passable
struct Pair:
var a: Int
var b: Int
fn __moveinit__(inout self, owned exisiting: Self):
self.a = exisiting.a
self.b = existing.b
error: Expression [11]:10:5: '__moveinit__' is not supported for @register_passable types, they are always movable by copying a register
fn __moveinit__(inout self, owned exisiting: Self):
^
error: Expression [11]:12:18: use of unknown declaration 'existing', 'fn' declarations require explicit variable declarations
self.b = existing.b
^~~~~~~~
@register_passable("trivial")
This means you can't define:
__init__
__copyinit__
__moveinit__
__del__
It's referred to as trivial
because it is always pass by copy/value, there is no special logic required for destruction, construction, indirection or anything else. You can think of it like a Int64
contains just 64 bits of data, generally lives on the stack, and can be copied straight into registers. You don't need any special allocation or memory freeing behaviour because it's trivial
, copying it around everywhere is the most efficient way to use it. Right now Mojo's generics only work with trivial types because the compiler can treat these trivial types the same, while it can't generalize on objects that require special constructor and destructor behaviour. This will be resolved when traits
are introduced.
Examples of trivial types:
- 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
- Struct types decorated with
@register_passable("trivial")
, that can only contain other trivial types:
@register_passable("trivial")
struct Pair:
var a: Int
var b: Int