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).
Explicit destruction
Explicit destruction prevents ASAP destruction from destroying a value at
its last use. To opt-in, conform the type to an unsatisfiable Deinitable
constraint:
struct Example(Deinitable where False):
def cleanup(deinit self):
pass
This disables automatic destruction via the __deinit__() method
and requires explicit cleanup through named deinitializer methods. When using
this pattern, you must call a named deinitializer method to consume the value.
If you don't, the compiler emits an error.
Use explicit destruction when cleanup must be performed deliberately, may fail and require error handling, or your type offers multiple valid ways to end a value's lifetime (such as saving to file and closing the file descriptor, or quitting without saving).
Implicit vs. explicit destruction
Mojo supports two destruction models. Most types rely on compiler-managed lifetime analysis, while some types opt into explicit control for stronger cleanup guarantees.
Implicit destruction (the default): The compiler automatically calls
__deinit__() when a value has no further uses. Cleanup is triggered by Mojo's
lifetime analysis and requires no manual intervention. You may override
__deinit__() in your type, but the compiler does not verify that cleanup is
performed intentionally.
Explicit destruction: You intentionally call named deinitializer methods
(such as cleanup() or save_and_close()). The conformance disables
automatic destruction and the compiler enforces explicit consumption.
Failing to call a deinitializer before a value leaves scope results in a
compile-time error.
Most types use implicit destruction. Explicit destruction adds a layer of safety when cleanup may fail, requires error handling, or benefits from deliberate control over how a value's lifetime ends.
Basic usage
To opt into explicit, compiler-enforced destruction, mark types with
Deinitable where False.
Provide named deinitializer methods that use the deinit self argument
convention:
struct FileBuffer(Deinitable where False):
var path: String
var data: String
def __init__(out self, path: String):
self.path = path
self.data = ""
def write(mut self, content: String):
self.data += content
def save_and_close(deinit self) raises:
write_to_disk(self.path, self.data)
Declaring types with Deinitable where False requires you to add
intentional calls to type-specific deinitializer methods before the end of
scope or to transfer the value out of scope so it can be consumed at a
later time:
def write_log(path: String, message: String) raises:
var buffer = FileBuffer(path)
buffer.write(message)
buffer^.save_and_close() # Required before `buffer` leaves scope
If you omit this call, the compiler emits an error.
Custom error messages
To improve compiler diagnostics for explicit destruction, include a
custom error message with where constraint:
struct CustomFileHandle(
Deinitable where(False, "Must call save_and_close() or discard()")
):
# ... other implementation details ...
def save_and_close(deinit self) raises:
write_to_disk(self.path, self.data) # Store data, then end
self.close_descriptor()
def discard(deinit self):
pass # Abandon without writing
Related
- Death of a value - Complete coverage of value destruction and lifetime management
AnyType- Base trait for all typesDeinitable- Trait for automatically deinitializable types