For the complete Mojo documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).
Automatic value destruction
Mojo destroys values as soon as they're no longer used. It doesn't wait for
the end of a code block or even the end of an expression. With Mojo's
as-soon-as-possible (ASAP) destruction policy, intermediate values in an
expression such as a + (b - c) * d can be destroyed as soon as their last
use completes.
At compile time, Mojo determines the last use of each value. After its last
use, the value's lifetime ends and Mojo calls its __deinit__()
deinitializer.
For cleanup that must happen at a specific, compiler-checked point, Mojo also supports explicit deinitializers. See Requiring explicit destruction.
When Mojo destroys values
To see when each Number instance is destroyed, give Number a
deinitializer that prints a message:
@fieldwise_init
struct Number(Writable):
var value: Int
# Track the destruction of each Number instance
def __deinit__(deinit self):
print(t"Destroying Number(value={self.value})")
# Add two Number values together
def __add__(self, other: Number) -> Number:
return Number(self.value + other.value)
# Subtract a Number value from another
def __sub__(self, other: Number) -> Number:
return Number(self.value - other.value)
# Multiply two Number values together
def __mul__(self, other: Number) -> Number:
return Number(self.value * other.value)
def main():
var a = Number(1)
var b = Number(2)
var c = Number(3)
var d = Number(4)
# 1 2 3 4
print(a + (b - c) * d)
# The output shows the order of destruction for each value.
# Expression precedence determines the order of evaluation, and
# therefore the order in which these last uses occur:
# Destroying Number(value=3) # PARENTHESIZED SUBTRACTION FIRST
# Destroying Number(value=2) # PARENTHESIZED SUBTRACTION FIRST
# Destroying Number(value=-1) # MULTIPLICATION SECOND, intermediate value
# Destroying Number(value=4) # MULTIPLICATION SECOND
# Destroying Number(value=-4) # ADDITION THIRD, intermediate value
# Destroying Number(value=1) # ADDITION THIRD
# Number(value=-3) # PRINTS RESULT
# Destroying Number(value=-3) # RESULT IS DESTROYED; DEINITIALIZER RUNS
Every value is initialized once and deinitialized once. This happens as the expression is evaluated in precedence order: the parenthesized subtraction first, then multiplication, then addition. Values are destroyed as soon as their last use completes.
Intermediate values that aren't bound to variables (-1, -4, and -3 in this example) also have lifecycles. They're destroyed after their last use.
Deinitializer behavior
__deinit__() uses the deinit
argument convention
for self. The value and its fields remain valid while __deinit__()
performs cleanup. When the method returns, the instance becomes logically
deinitialized.
If a struct doesn't define __deinit__() and doesn't opt out with
not Deinitable, Mojo generates one. That requires every field to be
Deinitable. A struct that holds an explicitly destroyed field must
either define a __deinit__() that consumes the field or opt out itself.
In general, don't call __deinit__() directly. If you need that level of
control, use explicit value
destruction. You may also need
to call __deinit__() directly when wrapping a deinitializer for
parameterized types.
Moving values out of fields
Deinitializers are the only place where you can safely move values out of an instance's fields without having to reinitialize the field before its next use.
This special rule isn't about where the code lives. It's about guarantees:
def __deinit__(deinit self):
var name = self.name^ # OK: can take ownership of fields
The compiler knows that deinitializers like __deinit__() are the final
use of an instance. Because of this, it allows you to move values out of
fields without reinitializing them.
This ensures that fields are only moved out of struct instances when the compiler can guarantee the instance is at the end of its lifetime.
Like instances, struct fields use ASAP destruction within __deinit__()
methods. For example:
struct S:
var a: String
var b: String
def __deinit__(deinit self):
# Mojo calls self.a.__deinit__() here.
use(self.b)
# Mojo calls self.b.__deinit__() here.
Custom deinitializers
Define a custom
__deinit__()
when your type needs to perform cleanup as it's destroyed. For example, you
might free manually allocated memory or close a long-lived resource such as a
file.
This logger owns a temporary file. Its deinitializer closes the file when the logger is destroyed:
from std.tempfile import NamedTemporaryFile
struct QuickLogger:
var temporary_file: NamedTemporaryFile
def __init__(out self) raises:
self.temporary_file = NamedTemporaryFile(mode="w", delete=False)
# Log a message to the temporary file
def log(mut self, message: String) raises:
self.temporary_file.write(message + "\n")
def __deinit__(deinit self):
try:
print(t"Closing: {self.temporary_file.name}")
self.temporary_file^.close()
except e:
print(t"Error: {e}")
def main() raises:
var ql = QuickLogger()
ql.log("This is a test log message.")
ql.log("This is the last use of 'ql'")
Keeping a value alive
Most of the time, Mojo's ASAP destruction picks the right point and ends a value's lifetime there. Sometimes, though, something outside a value's ordinary uses still depends on it, such as a pointer into its storage or a lock it holds. Origins keep the value alive for pointers the compiler can track. For everything else, extend the lifetime explicitly.
Origins keep owners alive
A pointer whose origin ties back to the owner keeps the owner alive automatically:
def main():
var foo = String("some text")
var bar = foo.unsafe_ptr() # Pointer[Byte, origin_of(foo)]
# time passes, and foo is never read directly again
print(bar[]) # using bar here keeps foo alive to this point
The same holds for a pointer to one of a value's fields. Its origin keeps the whole value alive:
def main():
var b = Buf(v=42)
var p = Pointer(to=b.v) # origin_of(b.v)
print("midpoint")
use_ptr(p) # b is still alive
# Buf destroyed after p's last use, not after the second line
The compiler pushes you toward origins. It rejects AnyOrigin in a struct
field outright, and it refuses to widen Pointer[Int, origin_of(a._alloc)]
to Pointer[Int, MutAnyOrigin] implicitly.
When working with foreign function interfaces (FFIs), prefer an origin tied to the owner of the value you pull a pointer from. For worked examples of C filling a Mojo-owned buffer, see Calling C functions.
When the origin is gone
Without an origin, a dependency is invisible and the owner dies after the
pointer construction and origin cast. Casting to MutUntrackedOrigin
severs the link. Here's the same code with the origin removed:
def main():
var b = Buf(v=42)
var p = Pointer(to=b.v).unsafe_origin_cast[MutUntrackedOrigin]()
# Buf destroyed here, before midpoint
print("midpoint")
use_ptr(p) # undefined behavior: the owner is already gone
Origins are unavailable or gone after a cast to an untracked origin, with an opaque handle, or with a bare address crossing into C. Those produce an untracked pointer, and the compiler stops connecting it to the owner. That's when you extend the lifetime yourself.
If the C side retains a pointer past the call, no origin saves you, because the retention is invisible. That case needs an owner that outlives the C-side lifetime, not a lifetime extension.
Explicit lifetime extension
Use explicit lifetime extension when something outside the value's ordinary uses still requires it to remain alive and you can't use origins or references to do this.
Assign the value to the _ discard
pattern where you want its lifetime
to end. This marks its last use, so the deinitializer runs immediately after the
statement:
var s = "abc"
print(s) # s.__deinit__() runs after this line
# Extend t's lifetime to the discard line
var t = "xyz"
print(t)
# ... some time later
_ = t # t.__deinit__() runs after this line
Two cases particularly need explicit lifetime extension: an RAII guard, such
as a lock, that would otherwise be released too early, and a pointer whose
origin has been erased, which can lead to a use-after-free. Neither produces
a compiler error. _ = value keeps the value alive until the point where you
need it released or need the pointer to remain valid.
The transfer sigil makes no difference here: _ = t^ keeps t alive
exactly as _ = t does.
Assigning to the discard pattern can only extend the lifetime of a value, it never forces a value to be destroyed.