Skip to main content

Life of a value

The life of a value in Mojo begins when a variable is initialized and continues up until the value is last used, at which point Mojo destroys it. This page describes how every value in Mojo is created, copied, and moved. (The next page describes how values are destroyed.)

All data types in Mojo—including basic types in the standard library such as Bool, Int, and String, up to complex types such as SIMD—are defined as structs. This means the creation and destruction of any piece of data follows the same lifecycle rules, and you can define your own data types that work exactly the same way.

Mojo structs don't get any default lifecycle methods, such as a constructor, copy constructor, or move constructor. That means you can create a struct without a constructor, but then you can't instantiate it, and it would be useful only as a sort of namespace for static methods. For example:

struct NoInstances:
var state: Int

@staticmethod
fn print_hello():
print("Hello world!")
struct NoInstances:
var state: Int

@staticmethod
fn print_hello():
print("Hello world!")

Without a constructor, this cannot be instantiated, so it has no lifecycle. The state field is also useless because it cannot be initialized (Mojo structs do not support default field values—you must initialize them in a constructor).

So the only thing you can do is call the static method:

NoInstances.print_hello()
NoInstances.print_hello()
Hello world!
Hello world!

Constructor

To create an instance of a Mojo type, it needs the __init__() constructor method. The main responsibility of the constructor is to initialize all fields. For example:

struct MyPet:
var name: String
var age: Int

fn __init__(out self, name: String, age: Int):
self.name = name
self.age = age
struct MyPet:
var name: String
var age: Int

fn __init__(out self, name: String, age: Int):
self.name = name
self.age = age

Now we can create an instance:

var mine = MyPet("Loki", 4)
var mine = MyPet("Loki", 4)

An instance of MyPet can also be read and destroyed, but it currently can't be copied or moved.

We believe this is a good default starting point, because there are no built-in lifecycle events and no surprise behaviors. You—the type author—must explicitly decide whether and how the type can be copied or moved, by implementing the copy and move constructors.

The pattern shown above—a constructor that takes an argument for each of the struct's fields and initializes the fields directly from the arguments—is called a field-wise constructor. It's a common enough pattern that Mojo includes a @fieldwise_init decorator to synthesize the field-wise constructor. So you can rewrite the previous example like this:

@fieldwise_init
struct MyPet:
var name: String
var age: Int
@fieldwise_init
struct MyPet:
var name: String
var age: Int

Overloading the constructor

Like any other function/method, you can overload the __init__() constructor to initialize the object with different arguments. For example, you might want a default constructor that sets some default values and takes no arguments, and then additional constructors that accept more arguments.

Just be aware that, in order to modify any fields, each constructor must declare the self argument with the out convention. If you want to call one constructor from another, you simply call upon that constructor as you would externally (you don't need to pass self).

For example, here's how you can delegate work from an overloaded constructor:

struct MyPet:
var name: String
var age: Int

fn __init__(out self):
self.name = ""
self.age = 0

fn __init__(out self, name: String):
self = MyPet()
self.name = name
struct MyPet:
var name: String
var age: Int

fn __init__(out self):
self.name = ""
self.age = 0

fn __init__(out self, name: String):
self = MyPet()
self.name = name

Field initialization

Notice in the previous example that, by the end of each constructor, all fields must be initialized. That's the only requirement in the constructor.

In fact, the __init__() constructor is smart enough to treat the self object as fully initialized even before the constructor is finished, as long as all fields are initialized. For example, this constructor can pass around self as soon as all fields are initialized:

fn use(arg: MyPet):
pass

struct MyPet:
var name: String
var age: Int

fn __init__(out self, name: String, age: Int, cond: Bool):
self.name = name
if cond:
self.age = age
use(self) # Safe to use immediately!

self.age = age
use(self) # Safe to use immediately!
fn use(arg: MyPet):
pass

struct MyPet:
var name: String
var age: Int

fn __init__(out self, name: String, age: Int, cond: Bool):
self.name = name
if cond:
self.age = age
use(self) # Safe to use immediately!

self.age = age
use(self) # Safe to use immediately!

Constructors and implicit conversion

Mojo supports implicit conversion from one type to another. Implicit conversion can happen when one of the following occurs:

  • You assign a value of one type to a variable with a different type.
  • You pass a value of one type to a function that requires a different type.
  • You return a value of one type from a function that specifies a different return type.

In all cases, implicit conversion is supported when the target type defines a constructor that meets the following criteria:

  • Is declared with the @implicit decorator.
  • Has a single required, non-keyword argument of the source type.

For example:

var a = Source()
var b: Target = a
var a = Source()
var b: Target = a

Mojo implicitly converts the Source value in a to a Target value if Target defines a matching constructor like this:

struct Target:

@implicit
fn __init__(out self, s: Source): ...
struct Target:

@implicit
fn __init__(out self, s: Source): ...

With implicit conversion, the assignment above is essentially identical to:

var b = Target(a)
var b = Target(a)

In general, types should only support implicit conversions when the conversion lossless, and ideally inexpensive. For example, converting an integer to a floating-point number is usually lossless (except for very large positive and negative integers, where the conversion may be approximate), but converting a floating-point number to an integer is very likely to lose information. So Mojo supports implicit conversion from Int to Float64, but not the reverse.

The constructor used for implicit conversion can take optional arguments, so the following constructor would also support implicit conversion from Source to Target:

struct Target:

@implicit
fn __init__(out self, s: Source, reverse: Bool = False): ...
struct Target:

@implicit
fn __init__(out self, s: Source, reverse: Bool = False): ...

Implicit conversion can fail if Mojo can't unambiguously match the conversion to a constructor. For example, if the target type has two overloaded constructors that take different types, and each of those types supports an implicit conversion from the source type, the compiler has two equally-valid paths to convert the values:

struct A:
@implicit
fn __init__(out self, s: Source): ...

struct B:
@implicit
fn __init__(out self, s: Source): ...

struct OverloadedTarget:
@implicit
fn __init__(out self, a: A): ...
@implicit
fn __init__(out self, b: B): ...

var t = OverloadedTarget(Source()) # Error: ambiguous call to '__init__': each
# candidate requires 1 implicit conversion
struct A:
@implicit
fn __init__(out self, s: Source): ...

struct B:
@implicit
fn __init__(out self, s: Source): ...

struct OverloadedTarget:
@implicit
fn __init__(out self, a: A): ...
@implicit
fn __init__(out self, b: B): ...

var t = OverloadedTarget(Source()) # Error: ambiguous call to '__init__': each
# candidate requires 1 implicit conversion

In this case, you can fix the issue by explicitly casting to one of the intermediate types. For example:

var t = OverloadedTarget(A(Source())) # OK
var t = OverloadedTarget(A(Source())) # OK

Mojo applies at most one implicit conversion to a variable. For example:

var t: OverloadedTarget = Source() # Error: can't implicitly convert Source
# to Target
var t: OverloadedTarget = Source() # Error: can't implicitly convert Source
# to Target

Would fail because there's no direct conversion from Source to OverloadedTarget.

For structs with a single field, you can generate an implicit constructor with the @fieldwise_init("implicit") decorator.

@fieldwise_init("implicit")
struct Counter:
var count: Int

def main():
var c: Counter = 5 # implicitly converts from Int
@fieldwise_init("implicit")
struct Counter:
var count: Int

def main():
var c: Counter = 5 # implicitly converts from Int

Copy constructor

A Mojo type is copyable if one of the following is true:

  • It has a copy constructor.
  • It's a trivial type like Int, which is trivially copyable and movable.

When Mojo encounters an assignment statement that doesn't use the transfer sigil (^), it tries to make a copy of the right-side value by calling upon that type's copy constructor: the __copyinit__() method.

For example, the MyPet type above does not have a copy constructor, so this code fails to compile:

var mine = MyPet("Loki", 4)
var yours = mine # This requires a copy, but MyPet has no copy constructor
var mine = MyPet("Loki", 4)
var yours = mine # This requires a copy, but MyPet has no copy constructor

To make a struct copyable, you need to:

  • Add the Copyable trait.
  • (Optionally) define a custom __copyinit__() method if needed.

If you simply add the Copyable trait, Mojo will generate a default __copyinit__() method for you, which copies each field of the existing value into the new value. The following example shows what the default copy constructor would look like for MyPet:

@fieldwise_init
struct MyPet(Copyable):
var name: String
var age: Int
@fieldwise_init
struct MyPet(Copyable):
var name: String
var age: Int

Now this code works to make a copy:

var mine = MyPet("Loki", 4)
var yours = mine
var mine = MyPet("Loki", 4)
var yours = mine

The generated copy constructor simply copies each field from the existing value into the new value. For example, if you wrote the __copyinit__() method for MyPet, it would look like this:

fn __copyinit__(out self, existing: Self):
self.name = existing.name
self.age = existing.age
fn __copyinit__(out self, existing: Self):
self.name = existing.name
self.age = existing.age

This default copy constructor works in most cases, but there are a few cases where you need to define a custom copy constructor:

  • One or more of the struct's fields is not Copyable.
  • The struct includes a non-owning type (like UnsafePointer), and you want to make a deep copy of the data.
  • The struct holds other resources (like file descriptors or network sockets) that need to be managed.

Custom copy constructor

What makes Mojo's copy behavior different, compared to other languages, is that __copyinit__() is designed to perform a deep copy of all fields in the type (as per value semantics). That is, it copies heap-allocated values, rather than just copying the pointer.

However, the Mojo compiler doesn't enforce this, so it's the type author's responsibility to implement __copyinit__() with value semantics.

For example, here's a new HeapArray type with a custom copy constructor that performs a deep copy:

struct HeapArray(Copyable):
var data: UnsafePointer[Int]
var size: Int
var cap: Int

fn __init__(out self, size: Int, val: Int):
self.size = size
self.cap = size * 2
self.data = UnsafePointer[Int].alloc(self.cap)
for i in range(self.size):
(self.data + i).init_pointee_copy(val)

fn __copyinit__(out self, existing: Self):
# Deep-copy the existing value
self.size = existing.size
self.cap = existing.cap
self.data = UnsafePointer[Int].alloc(self.cap)
for i in range(self.size):
(self.data + i).init_pointee_copy(existing.data[i])
# The lifetime of `existing` continues unchanged

fn __del__(owned self):
# We must free the heap-allocated data, but
# Mojo knows how to destroy the other fields
for i in range(self.size):
(self.data + i).destroy_pointee()
self.data.free()

fn append(mut self, val: Int):
# Update the array for demo purposes
if self.size < self.cap:
(self.data + self.size).init_pointee_copy(val)
self.size += 1
else:
print("Out of bounds")

fn dump(self):
# Print the array contents for demo purposes
print("[", end="")
for i in range(self.size):
if i > 0:
print(", ", end="")
print(self.data[i], end="")
print("]")
struct HeapArray(Copyable):
var data: UnsafePointer[Int]
var size: Int
var cap: Int

fn __init__(out self, size: Int, val: Int):
self.size = size
self.cap = size * 2
self.data = UnsafePointer[Int].alloc(self.cap)
for i in range(self.size):
(self.data + i).init_pointee_copy(val)

fn __copyinit__(out self, existing: Self):
# Deep-copy the existing value
self.size = existing.size
self.cap = existing.cap
self.data = UnsafePointer[Int].alloc(self.cap)
for i in range(self.size):
(self.data + i).init_pointee_copy(existing.data[i])
# The lifetime of `existing` continues unchanged

fn __del__(owned self):
# We must free the heap-allocated data, but
# Mojo knows how to destroy the other fields
for i in range(self.size):
(self.data + i).destroy_pointee()
self.data.free()

fn append(mut self, val: Int):
# Update the array for demo purposes
if self.size < self.cap:
(self.data + self.size).init_pointee_copy(val)
self.size += 1
else:
print("Out of bounds")

fn dump(self):
# Print the array contents for demo purposes
print("[", end="")
for i in range(self.size):
if i > 0:
print(", ", end="")
print(self.data[i], end="")
print("]")

Notice that __copyinit__() does not copy the UnsafePointer value (doing so would make the copied value refer to the same data memory address as the original value, which is a shallow copy). Instead, we initialize a new UnsafePointer to allocate a new block of memory, and then copy over all the heap-allocated values (this is a deep copy).

Thus, when we copy an instance of HeapArray, each copy has its own set of values on the heap, so changes to one array do not affect the other, as shown here:

fn copies():
var a = HeapArray(2, 1)
var b = a # Calls the copy constructor
a.dump() # Prints [1, 1]
b.dump() # Prints [1, 1]

b.append(2) # Changes the copied data
b.dump() # Prints [1, 1, 2]
a.dump() # Prints [1, 1] (the original did not change)
fn copies():
var a = HeapArray(2, 1)
var b = a # Calls the copy constructor
a.dump() # Prints [1, 1]
b.dump() # Prints [1, 1]

b.append(2) # Changes the copied data
b.dump() # Prints [1, 1, 2]
a.dump() # Prints [1, 1] (the original did not change)

Two other things to note from the __copyinit__() method:

  • The existing argument is type Self (capital "S"). Self is an alias for the current type name (HeapArray, in this example). Using this alias is a best practice to avoid any mistakes when referring to the current struct name.

  • The existing argument is immutable because the default argument convention is read—this is a good thing because this function should not modify the contents of the value being copied.

If your type doesn't use any pointers for heap-allocated data, then writing the constructor and copy constructor is all boilerplate code that you shouldn't have to write. For most structs that don't manage memory explicitly, you can just add the Copyable trait to your struct definition and Mojo will synthesize the __copyinit__() method.

Explicitly copyable types

If you want to avoid accidental copies at all costs, but want to allow users to copy your type intentionally, you can omit the copy constructor and Copyable trait, and implement the ExplicitlyCopyable trait instead, which describes a type that can be explicitly copied by calling its copy() method.

This can be useful for types that dynamically allocate memory, where you want to be very intentional about when you make a deep copy of the value.

Likewise, when designing a collection type, you could require the collection elements to be ExplicitlyCopyable rather than Copyable. For example, the following container stores a single element, which must be ExplictlyCopyable and Movable:

struct ExplicitCopyOnly[ElementType: ExplicitlyCopyable & Movable](
ExplicitlyCopyable, Movable
):
var ptr: UnsafePointer[ElementType]

fn __init__(out self, owned elt: ElementType):
"""Constructs a new container, storing the given value."""
self.ptr = UnsafePointer[ElementType].alloc(1)
self.ptr.init_pointee_move(elt^)

fn copy(self) -> Self:
"""Performs a deep copy of this container."""
elt_copy = self.ptr[].copy()
copy = Self(elt_copy^)
return copy^

fn __getitem__(ref self) -> ref [self] ElementType:
"""Returns a reference to the stored value."""
return self.ptr[]
struct ExplicitCopyOnly[ElementType: ExplicitlyCopyable & Movable](
ExplicitlyCopyable, Movable
):
var ptr: UnsafePointer[ElementType]

fn __init__(out self, owned elt: ElementType):
"""Constructs a new container, storing the given value."""
self.ptr = UnsafePointer[ElementType].alloc(1)
self.ptr.init_pointee_move(elt^)

fn copy(self) -> Self:
"""Performs a deep copy of this container."""
elt_copy = self.ptr[].copy()
copy = Self(elt_copy^)
return copy^

fn __getitem__(ref self) -> ref [self] ElementType:
"""Returns a reference to the stored value."""
return self.ptr[]

You can construct and use the container like this:

    big = BigExpensiveStruct()
original = ExplicitCopyOnly(big^)
copy = original.copy()
    big = BigExpensiveStruct()
original = ExplicitCopyOnly(big^)
copy = original.copy()

Because it's explicitly copyable, you can't accidentally trigger a deep copy of the container and its BigExpensiveStruct:

implicit_copy = original
implicit_copy = original
error: 'ExplicitCopyOnly[BigExpensiveStruct]' is not copyable because it
has no '__copyinit__'
error: 'ExplicitCopyOnly[BigExpensiveStruct]' is not copyable because it
has no '__copyinit__'

Note that a type can be both Copyable and ExplicitlyCopyable. If your type is already Copyable, you can get explicit copyability for free by adding the ExplicitlyCopyable trait. In this case, the compiler adds a trivial implementation of copy(), which calls the copy constructor. This lets you use the type with any generic function or struct that requires the ExplicitlyCopyable trait.

Move constructor

A type is moveable if one of the following is true:

Although copying values provides predictable behavior that matches Mojo's value semantics, copying some data types can be a significant hit on performance.

Mojo uses the move constructor to transfer ownership of a value from one variable to another, without copying its fields.

To add a move constructor to a type:

  • Add the Movable trait.
  • (Optionally) implement a custom __moveinit__() method.

So here's a movable version of the MyPet struct:

@fieldwise_init
struct MyPet(Copyable, Movable):
var name: String
var age: Int
@fieldwise_init
struct MyPet(Copyable, Movable):
var name: String
var age: Int

Here's an example showing how to invoke the move constructor for MyPet:

fn moves():
var a = MyPet("Bobo", 2)

print(a.name) # Prints "bobo"

var b = a^ # the lifetime of `a` ends here

print(b.name) # prints "bobo"
# print(a.name) # ERROR: use of uninitialized value 'a'
fn moves():
var a = MyPet("Bobo", 2)

print(a.name) # Prints "bobo"

var b = a^ # the lifetime of `a` ends here

print(b.name) # prints "bobo"
# print(a.name) # ERROR: use of uninitialized value 'a'

If you include the Movable trait and don't define a move constructor, Mojo generates a default move constructor for you. This move constructor simply moves each of the fields to the new instance.

The generated move constructor for MyPet would look like this if you wrote it yourself:

fn __moveinit__(out self, owned existing: Self):
self.name = existing.name^
self.age = existing.age
fn __moveinit__(out self, owned existing: Self):
self.name = existing.name^
self.age = existing.age

The move constructor uses the transfer sigil (^) to indicate that ownership of the value is being transferred from existing to self. For register-passable types like Int, the transfer sigil is omitted: register-passable types are always treated as movable, but they can't define custom move constructors or destructors, so there's no special logic to run for a move.

At the end of the __moveinit__() method, Mojo immediately invalidates the original variable, preventing any access to it. It does not call the destructor, since that would destroy resources that have been transferred to the new instance. Invalidating the original variable is important to avoid memory errors on heap-allocated data, such as use-after-free and double-free errors.

Custom move constructor

In practice, structs very rarely require a custom __moveinit__(). A type might require a custom __moveinit__() if it has a pointer to itself or one of its fields, for example, since the struct's location in memory changes when it's moved.

The __moveinit__() method performs a consuming move: it transfers ownership of a value from one variable to another when the original variable's lifetime ends (also called a "destructive move").

A critical feature of __moveinit__() is that it takes the incoming value as owned, meaning this method gets unique ownership of the value. Moreover, because this is a dunder method that Mojo calls only when performing a move (during ownership transfer), the existing argument is guaranteed to be a mutable reference to the original value, not a copy (unlike other methods that may declare an argument as owned, but might receive the value as a copy if the method is called without the ^ transfer sigil). That is, Mojo calls this move constructor only when the original variable's lifetime actually ends at the point of transfer.

Move-only and immovable types

To ensure your type can't be implicitly copied, you can make it "move-only" by making it Movable but not Copyable. A move-only type can be passed to other variables and passed into functions with any argument convention (read, mut, and owned)—the only catch is that you must use the ^ transfer sigil to end the lifetime of a move-only type when assigning it to a new variable or when passing it as an owned argument. The OwnedPointer is an example of a move-only type: because it is designed to provide clear single ownership of a stored value, the OwnedPointer can be moved, but not copied.

In some (rare) cases, you may not want a type to be copyable or movable. The Atomic type is an example of a type that's neither copyable or movable.

Simple value types

Up to version 25.5, Mojo provided a @value decorator that generated a field-wise constructor, copy constructor, and move constructor for a struct. Because users frequently want to take advantage of just one or two of these generated methods, the @value decorator was deprecated in v25.5 in favor of separate mechanisms:

  • For a field-wise constructor, use the @fieldwise_init decorator as described in the section on constructors.

  • For a default copy constructor, use the Copyable trait, as described in the section on copy constructors.

  • For a default move constructor, use the Movable trait, as described in the section on move constructors.

Trivial types

So far, we've talked about values that live in memory, which means they have an identity (an address) that can be passed around among functions (passed "by reference"). This is great for most types, and it's a safe default for large objects with expensive copy operations. However, it's inefficient for tiny things like a single integer or floating point number. We call these types "trivial" because they are just "bags of bits" that should be copied, moved, and destroyed without invoking any custom lifecycle methods.

Trivial types are the most common types that surround us, and from a language perspective, Mojo doesn't need special support for these written in a struct. Usually, these values are so tiny that they should be passed around in CPU registers, not indirectly through memory.

As such, Mojo provides a struct decorator to declare these types of values: @register_passable("trivial"). This decorator tells Mojo that the type should be copyable and movable but that it has no user-defined logic for this (no custom copy constructor or move constructor). It also tells Mojo to pass the value in CPU registers whenever possible, which has clear performance benefits.

You'll see this decorator on types like Int in the standard library:

@register_passable("trivial")
struct Int:
...
@register_passable("trivial")
struct Int:
...

We expect to use this decorator pervasively on Mojo standard library types, but it is safe to ignore for general application-level code.

For more information, see the @register_passable documentation.

Was this page helpful?