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).
Intro to value lifecycle
So far, we've explained how Mojo allows you to build high-performance code that is memory safe without manually managing memory, using Mojo's ownership model. However, Mojo is designed for systems programming, which often requires manual memory management for custom data types. So, Mojo lets you do that as you see fit. To be clear, Mojo has no reference counter and no garbage collector.
Mojo also has no built-in data types with special privileges. All data types
in the standard library (such as Bool,
Int, and
String) are implemented as
structs.
What's great about the Mojo language is that it provides you these low-level tools for systems programming, but within a framework that helps you build things that are safe and easy to use from higher-level programs. That is, you can get under the hood and write all the "unsafe" code you want, but as long as you do so in accordance with Mojo's value semantics, the programmer instantiating your type/object doesn't need to think about memory management at all, and the behavior will be safe and predictable, thanks to value ownership.
In summary, it's the responsibility of the type author to manage the memory and resources for each value type, by implementing specific lifecycle methods, such as the initializer, copy initializer, move initializer, and deinitializer, as necessary. Mojo doesn't create any initializers by default, although it does add a trivial, no-op deinitializer for types that don't define their own.
In summary, it's the responsibility of the type author to manage the memory and resources for each value type, by implementing specific lifecycle methods, such as the initializer, copy initializer, move initializer, and deinitializer, as necessary.
Lifecycles and lifetimes
Some terminology:
- The "lifecycle" of a value is defined by various dunder
methods in a struct. Each
lifecycle event is handled by a different method, such as the initializer
(
__init__()), the deinitializer (__deinit__()), the copy initializer (__init__(copy=)), and the move initializer (__init__(take=)). All values that are declared with the same type have the same lifecycle. - The "lifetime" of a variable is defined by the span of time during
program execution in which the variable is considered valid. The life of
a variable begins when its value is initialized (via
__init__()) and ends when the value is deinitialized (__deinit__()), or consumed in some other way (for example, as part of a__init__(take=)call).
No two values have the exact same lifetime, because every value is created and destroyed at a different point in time (even if the difference is imperceptible).
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. Mojo destroys every value/object as soon as it's no longer used, using an "as soon as possible" (ASAP) destruction policy that runs after every sub-expression. The Mojo compiler takes care of releasing resources after last use when needed.
As you might imagine, keeping track of a value's life can be difficult if a value is shared across functions many times during the life of a program. However, Mojo makes this predictable partly through its value semantics and value ownership (both prerequisite readings for the following sections). The final piece of the puzzle for lifetime management is the value lifecycle: every value (defined in a struct) needs to implement key lifecycle methods that define how a value is created and destroyed.