Skip to main content

@explicit_destroy

The @explicit_destroy decorator disables automatic destruction via the __del__() method and requires explicit cleanup through named destructor methods. When applying this decorator, you must call a named destructor 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 __del__() when a value has no further uses. Cleanup is triggered by Mojo’s lifetime analysis and requires no manual intervention. You may override __del__() in your type, but the compiler does not verify that cleanup is performed intentionally.

Explicit destruction: You intentionally call named destructor methods (such as cleanup() or save_and_close()). The compiler disables automatic destruction and enforces explicit consumption. Failing to call a destructor 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 @explicit_destroy and provide named destructor methods that use the deinit self argument convention:

@explicit_destroy
struct FileBuffer:
    var path: String
    var data: String

    fn __init__(out self, path: String):
        self.path = path
        self.data = ""

    fn write(mut self, content: String):
        self.data += content

    fn save_and_close(deinit self) raises:
        write_to_disk(self.path, self.data)

Declaring @explicit_destroy requires you to add intentional calls to type-specific destructors before the end of scope:

fn 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 the decorator:

@explicit_destroy("Must call save_and_close() or discard()")
struct FileBuffer:
    fn save_and_close(deinit self) raises:
        write_to_disk(self.path, self.data) # Store data

    fn discard(deinit self):
        pass  # Abandon without writing

Was this page helpful?