#set document(title: "4.1 Intro to value lifecycle", author: "Modular Inc. / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 4.1#h(0.6em)Intro to value lifecycle So far, we've explained how Mojo allows you to build high-performance code that is memory safe #emph[without] manually managing memory, using Mojo's ownership model. However, Mojo is designed for #link("https://en.wikipedia.org/wiki/Systems_programming")[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 #link("https://mojolang.org/docs/std/builtin/bool/Bool/")[Bool], #link("https://mojolang.org/docs/std/builtin/int/Int/")[Int], and #link("https://mojolang.org/docs/std/collections/string/string/String/")[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 constructor, copy constructor, move constructor, and destructor, as necessary. Mojo doesn't create any constructors by default, although it does add a trivial, no-op destructor for types that don't define their own. In the following pages, we'll explain exactly how to define these lifecycle methods in accordance with value semantics so your types play nicely with value ownership. === Lifecycles and lifetimes First, let's clarify 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 constructor (\_\_init\_\_()), the destructor (\_\_del\_\_()), the copy constructor (\_\_init\_\_(copy=)), and the move constructor (\_\_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 destroyed (\_\_del\_\_()), 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). #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Origin type] The concept of lifetimes is related to the origin type, a Mojo primitive used to track ownership. For most Mojo programming, you won't need to work with origin values directly. For information, see Lifetimes, origins and references. ] 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.