> For the complete Mojo documentation index, see [llms.txt](/llms.txt).
> Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).

# Get started with Mojo

Get ready to learn Mojo! This tutorial gives you a tour of Mojo by building a
complete program that does much more than simply printing "Hello, world!"

We'll build a version of [Conway's Game of
Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life), which is a
simulation of self-replicating systems. If you haven't heard of it before,
don't worry—it will make sense when you see it in action. Let's get
started so you can learn Mojo programming basics, including the following:

- Using basic built-in types like `Int` and `String`
- Using a `List` to manage a sequence of values
- Creating custom types in the form of structs (data structures)
- Creating and importing Mojo modules
- Importing and using Python libraries

There's a lot to learn, but we've tried to keep the explanations simple. If you
just want to see the finished code, you can [get it on
GitHub](https://github.com/modular/modular/tree/main/mojo/examples/life).

:::tip Before you start

You can use Mac, Linux, or Windows with WSL. For details, see the
[system requirements](/docs/requirements/).

If you're using an AI coding assistant while following this tutorial,
install [Mojo agent skills](/docs/tools/skills) first. Models can
fall behind the current language. The skills update regularly with
the latest syntax and language features.

```bash
npx skills add modular/skills
```

:::

## 1. Create a Mojo project

To install Mojo, we recommend using [`pixi`](https://pixi.sh/latest/) (for
other options, see the [install guide](/install/)).

1. If you don't have `pixi`, you can install it with this command:

    ```sh
    curl -fsSL https://pixi.sh/install.sh | sh
    ```

2. Navigate to the directory where you want to create the project and execute:

    ```bash
    pixi init life \
      -c https://conda.modular.com/max/ -c conda-forge \
      && cd life
    ```

   This creates a project directory named `life`, adds the Modular conda package
   channel, and navigates into the directory.
   :::tip
   You can skip the `-c` options if you
   [add these channels as defaults](https://pixi.prefix.dev/latest/reference/pixi_configuration/#default-channels).
   :::

3. Install the `mojo` package:

    ```bash
    pixi add mojo
    ```

4. Now let's list the project contents:

    ```bash
    ls -A
    ```

    ```output
    .gitattributes
    .gitignore
    .pixi
    pixi.lock
    pixi.toml
    ```

You should see that the project directory contains:

- An initial `pixi.toml` manifest file, which defines the project
  dependencies and other features

- A [lock file](https://pixi.sh/latest/workspace/lockfile/) named `pixi.lock`,
  which specifies the transitive dependencies and actual package versions
  installed in the project's virtual environment

- A `.pixi` subdirectory containing the conda virtual environment for the
  project

- Initial `.gitignore` and `.gitattributes` files that you can optionally use if
  you plan to use Git version control with the project

:::note
Never edit the lock file directly. The `pixi` command automatically updates
the lock file if you edit the manifest file.
:::

Let's verify that our project is configured correctly by checking the
version of Mojo that's installed in our project's virtual environment.
`pixi run` executes a command in the project's virtual environment, so let's
use it to execute `mojo --version`:

```bash
pixi run mojo --version
```

You should see a version string indicating the version of Mojo installed, which
by default should be the latest version. You can view and edit the version for
your project in the dependencies list in the `pixi.toml` file.

Great! Now let's write our first Mojo program.

## 2. Create a "Hello, world" program

In the project directory, create a file named `life.mojo` containing the
following lines of code:

```mojo title="life.mojo"
# My first Mojo program!
def main():
    print("Hello, World!")
```

<!-- markdownlint-disable MD013 -->
:::note
You can use any editor or IDE that you like. If you're using
[Visual Studio Code](https://code.visualstudio.com/) you can take advantage of
the
[Mojo for Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=modular-mojotools.vscode-mojo),
which provides features like syntax highlighting, code completion, and debugging
support. For [Cursor](https://cursor.com/) and other editors that support VS
Code extensions, you can install the
[Mojo for Visual Studio Code extension](https://open-vsx.org/extension/modular-mojotools/vscode-mojo)
from the [Open VSX Registry](https://open-vsx.org/).
:::
<!-- markdownlint-enable MD013 -->

If you've programmed in Python before, this should look familiar.

- We're using the `def` keyword to define a function named `main`.
- You can use any number of spaces or tabs for indentation as long as you use
  the same indentation for the entire code block. We'll follow the [Python style
  guide](https://peps.python.org/pep-0008/) and use 4 spaces.
- This [`print()`](/docs/std/io/io/print/) function is a Mojo built-in,
  so it doesn't require an import.

An executable Mojo program *requires* you to define a no-argument `main()`
function as its entry point. Running the program automatically invokes the
`main()` function, and your program exits when the `main()` function returns.

To run the program, we first need to start a shell session in our project's
virtual environment:

```bash
pixi shell
```

Later on, when you want to exit the virtual environment, just type `exit`.

Now we can use the `mojo` command to run our program.

```bash
mojo life.mojo
```

```output
Hello, World!
```

Mojo is a compiled language, not an interpreted one like Python. When we run
our program like this, `mojo` performs [just-in-time
compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation) (JIT) and
then runs the result.

We can also compile our program into an executable file using [`mojo
build`](/docs/cli/build) like this:

```bash
mojo build life.mojo
```

By default, this saves an executable file named `life` to the current directory.

```bash
./life
```

```output
Hello, World!
```

## 3. Create and use variables

Let's extend this basic program by prompting the user for their name and
including it in the greeting. The built-in
[`input()`](/docs/std/io/io/input/) function accepts an optional
[`String`](/docs/std/collections/string/string/String/) argument to use as a
prompt and returns a `String` consisting of the characters the user entered
(with the newline character at the end stripped off).

Let's declare a variable, assign the return value from `input()` to it, and
build a customized greeting.

```mojo title="life.mojo"
def main() raises:
    var name: String = input("Who are you? ")
    var greeting: String = "Hi, " + name + "!"
    print(greeting)
```

Go ahead and run it:

```bash
mojo life.mojo
```

```output
Who are you? Edna
Hi, Edna!
```

Notice that this code uses a `String` type annotation that indicates the type of
value the variable can contain. The Mojo compiler performs [static type
checking](https://en.wikipedia.org/wiki/Type_system#Static_type_checking), which
means you'll encounter a compile-time error if your code tries to assign a
value of one type to a variable of a different type.

Mojo also supports implicitly declared variables, where you simply assign a
value to a new variable without using the `var` keyword or indicating its type.
We can replace the code we just entered with the following, and it works
exactly the same.

```mojo title="life.mojo"
def main() raises:
    name = input("Who are you? ")
    greeting = "Hi, " + name + "!"
    print(greeting)
```

However, implicitly declared variables still have a fixed type, which Mojo
automatically infers from the initial value assignment. In this example, both
`name` and `greeting` are inferred as `String` type variables. If you then try
to assign an integer value like 42 to the `name` variable, you'll get a
compile-time error because of the type mismatch. You can learn more about Mojo
variables in the [Variables](/docs/manual/variables/) section of the Mojo
manual.

## 4. Use Mojo `Int` and `List` types to represent the game state

As originally envisioned by John Conway, the game's "world" is an infinite,
two-dimensional grid of square cells, but for our implementation, we'll
constrain the grid to a finite size. A drawback of making the edges of the grid
a hard boundary is that there are fewer neighboring cells around the edges
compared to the interior, which tends to cause die-offs. Therefore, we'll model
the world as a toroid (a donut shape), where the top row is considered adjacent
to the bottom row, and the left column is considered adjacent to the right
column. This will come into play later when we implement the algorithm for
calculating each subsequent generation.

To keep track of the height and width of our grid, we'll use
[`Int`](/docs/std/builtin/int/Int/), which represents a signed integer of the
[word size](https://en.wikipedia.org/wiki/Word_(computer_architecture)) of the
CPU, typically 32 or 64 bits.

To represent the state of an individual cell, we'll use an `Int` value of 1
(populated) or 0 (unpopulated). Later, when we need to determine the number of
populated neighbors surrounding a cell, we can simply add the values of the
neighboring cells.

To represent the state of the entire grid, we need a [collection
type](/docs/manual/types#collection-types). The most appropriate for this use
case is [`List`](/docs/std/collections/list/List/), which is a
dynamically-sized sequence of values.

All values in a Mojo `List` must be the same type so the Mojo compiler can
ensure type safety. (For example, when we retrieve a value from a `List[Int]`,
the compiler knows the value is an `Int` and can verify that we use it
correctly.) Mojo collections are implemented as [generic
types](https://en.wikipedia.org/wiki/Generic_programming), so we can indicate
the type of values the specific collection will hold by specifying a
[type parameter](/docs/manual/parameters/#parameterized-structs) in square
brackets like this:

```mojo
# The List in row can contain only Int values
row = List[Int]()

# The List in names can contain only String values
names = List[String]()
```

We can also create a `List` with an initial set of values and let the compiler
infer the type. Use the *list literal* syntax and simply enclose the values in
square brackets (`[]`):

```mojo
# Create a List[Int] with the list literal syntax, inferring the type
nums2 = [12, -7, 64]

# which is equivalent to
nums2: List[Int] = [12, -7, 64]
```

The Mojo `List` type includes the ability to append to the list, pop values from
the list, and access list items using subscript notation. Here's a taste of
those operations:

```mojo
nums = [12, -7, 64]
nums.append(-937)
print("Number of elements in the list:", len(nums))
print("Popping last element off the list:", nums.pop())
print("First element of the list:", nums[0])
print("Second element of the list:", nums[1])
print("Last element of the list:", nums[len(nums) - 1])
```

```output
Number of elements in the list: 4
Popping last element off the list: -937
First element of the list: 12
Second element of the list: -7
Last element of the list: 64
```

We can also nest `List`s:

```mojo
grid = [
    [11, 22],
    [33, 44]
]
print("Row 0, Column 0:", grid[0][0])
print("Row 0, Column 1:", grid[0][1])
print("Row 1, Column 0:", grid[1][0])
print("Row 1, Column 1:", grid[1][1])
```

```output
Row 0, Column 0: 11
Row 0, Column 1: 22
Row 1, Column 0: 33
Row 1, Column 1: 44
```

This looks like a good way to represent the state of the grid for our program.
Let's update the `main()` function with the following code that defines an
8×8 grid containing the initial state of a
"[glider](https://en.wikipedia.org/wiki/Glider_(Conway%27s_Game_of_Life))"
pattern.

Notice that we've removed `raises` from `main()`. In our earlier steps,
`main()` called `input()`, which can raise an error, so it needed the `raises`
keyword. Mojo functions are non-raising by default—you only need
`raises` when a function can propagate an error to its caller. Since
`main()` no longer calls
any raising functions, we can omit it. For more about error handling, see
[Errors, error handling, and context managers](/docs/manual/errors/).

```mojo title="life.mojo"
def main():
    num_rows = 8
    num_cols = 8
    glider = [
        [0, 1, 0, 0, 0, 0, 0, 0],
        [0, 0, 1, 0, 0, 0, 0, 0],
        [1, 1, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
    ]
```

### Copying values in Mojo

Before we move on, let's take a moment to discuss how Mojo handles copying
values. There's an important difference in Mojo between copying simple types
like `Int` and `String` and more complex types like `List`:

- An *explicitly copyable* type can be copied by calling its copy initializer or
  `copy()`. `List` is explicitly copyable, so if `first` is a `List`, you can
  copy it like this:

  ```mojo
  first = [1, 2, 3]
  second = first.copy()  # explicit copy
  ```

  This leaves `first` unchanged and assigns `second` its own, uniquely owned
  copy of the list.

- An *implicitly copyable* type can be copied without an explicit call to
  `copy()` or a
  copy initializer. `Int` and `String` are implicitly copyable types, so if
  `one_value` is an `Int`, you can copy it like this:

  ```mojo
  one_value = 15
  another_value = one_value  # implicit copy
  ```

  Here, `one_value` is unchanged, and `another_value` gets a copy of the value.

Implicit copying is useful for simple types like `Int` and `String`, where
copying is inexpensive and has no side effects. In contrast, a `List` might
occupy megabytes of memory, and unintentionally copying it could be a
significant performance hit. Therefore, the `List` type supports only explicit
copying to prevent accidental copying. Understanding this distinction will be
important when we define and use our own custom types later in this tutorial.

:::tip

You can determine whether a type is explicitly or implicitly copyable by
checking its API documentation to see what *traits* it conforms to. A Mojo
[trait](/docs/manual/traits/) is a set of requirements that a type must
implement, usually in the form of one or more method signatures.

- The `List` type and other Mojo collection types like
  [`Dict`](/docs/std/collections/dict/Dict/) and
  [`Set`](/docs/std/collections/set/Set/) conform to the
  [`Copyable`](/docs/std/builtin/value/Copyable/) trait, which indicates that
  they are **explicitly copyable**.

- The `String`, `Int` and other numeric types conform to the
  [`ImplicitlyCopyable`](/docs/std/builtin/value/ImplicitlyCopyable/) trait,
  which indicates that they are **implicitly copyable**. Additionally, the
  `ImplicitlyCopyable` trait refines the `Copyable` and `Movable` traits, so you
  can also use types that conform to the `ImplicitlyCopyable` trait in the same
  way you can use types that conform to the other traits.

:::

## 5. Create and use a function to print the grid

Now let's create a function using the `def` keyword to generate a
string representation of the game grid so we can print it to the
terminal. To learn more about defining functions in Mojo, see
[Functions](/docs/manual/functions/).

Let's add the following definition of a function named `grid_str()` to our
program. The Mojo compiler doesn't care whether we add our function before or
after `main()`, but the convention is to put `main()` at the end.

```mojo title="life.mojo"
def grid_str(rows: Int, cols: Int, grid: List[List[Int]]) -> String:
    # Create an empty String
    str = String()

    # Iterate through rows 0 through rows-1
    for row in range(rows):
        # Iterate through columns 0 through cols-1
        for col in range(cols):
            if grid[row][col] == 1:
                str += "*"  # If cell is populated, append an asterisk
            else:
                str += " "  # If cell is not populated, append a space
        if row != rows-1:
            str += "\n"     # Add a newline between rows, but not at the end
    return str
```

When we pass a value to a Mojo function, the default behavior is that an
argument is treated as an immutable reference to the value. This is
particularly useful for values like `List`s, where copying them could be
expensive. As we'll see later, we can specify different behavior by including an
explicit [argument passing
convention](/docs/manual/values/ownership#argument-passing-conventions).

Each argument name is followed by a type annotation indicating the type of value
you can pass to the argument. Just like when assigning a value to a variable,
you'll encounter a compile-time error if your code tries to pass a value of one
type to an argument of a different type. Finally, the `-> String` following the
argument list indicates that this function has a `String` type return value.

In the body of the function, we generate a `String` by appending an asterisk for
each populated cell and a space for each unpopulated cell, separating each row
of the grid with a newline character. We use nested `for` loops to iterate
through each row and column of the grid, using
[`range()`](/docs/std/builtin/range/range/) to generate a sequence of integers
from 0 up to (but not including) the given end value. Then we append the correct
characters to the `String` representation. See [Control
flow](/docs/manual/control-flow) for more information about `if`, `for`, and
other control flow structures in Mojo.

:::note

As described in [The `for`
statement](/docs/manual/control-flow#the-for-statement) section of the Mojo
manual, it's possible to iterate over the elements of a `List` directly instead
of iterating over the values of a `range()` and then accessing the `List`
elements by their numeric index.

```mojo
nums = [12, -7, 64]
for value in nums:
    print("Value:", value)
```

:::

Now that we've defined our `grid_str()` function, let's invoke it from `main()`.

```mojo title="life.mojo"
def main():
    ...
    result = grid_str(num_rows, num_cols, glider)
    print(result)
```

Then run the program to see the result:

```bash
mojo life.mojo
```

```output
 *
  *
***

```

We can see that the position of the asterisks matches the location of the 1s in
the `glider` grid.

## 6. Create a module and define a custom type

We're currently passing three arguments to `grid_str()` to describe the size and
state of the grid to print. A better approach would be to define our own custom
type that encapsulates all information about the grid. Then any function that
needs to manipulate a grid can accept a single argument. We can do this by
defining a Mojo *struct*, which is a custom data structure.

A [Mojo struct](/docs/manual/structs/) is a custom type consisting of:

- *Fields*, which are variables containing the data associated with the
  structure
- *Methods*, which are functions that we can optionally define to manipulate
  instances of the struct that we create
- Optionally, a set of traits that the struct conforms to

:::note

Mojo structs are similar to classes. However, Mojo structs do *not* support
inheritance. Mojo doesn't support classes at this time.

:::

We could define the struct in our existing `life.mojo` source file, but let's
create a separate *module* for the struct. A module is simply a Mojo source file
containing struct and function definitions that can be imported into other Mojo
source files. To learn more about creating and importing modules, see the
[Modules and packages](/docs/manual/packages/) section of the Mojo manual.

Create a new source file named `gridv1.mojo` in the project directory containing
the following definition of a struct named `Grid` with three fields:

```mojo title="gridv1.mojo"
@fieldwise_init
struct Grid(Copyable):
    var rows: Int
    var cols: Int
    var data: List[List[Int]]
```

Mojo requires you to declare all fields in the struct definition. You
can't add fields dynamically at run-time. You must declare the type for each
field, but you cannot assign a value as part of the field declaration.

The [constructor](/docs/manual/lifecycle/life/#constructor) is responsible for
initializing the value of all fields, as well as allocating additional resources
and performing any other configuration required by a new instance of the struct.
You implement a constructor by defining a method named `__init__()` in the
struct definition. Here's an example of how we *could* implement the
constructor for `Grid`:

```mojo
    def __init__(out self, rows: Int, cols: Int, var data: List[List[Int]]):
        self.rows = rows
        self.cols = cols
        self.data = data^
```

The first argument of a constructor is the newly created instance of the struct,
which by convention is named `self`. The Mojo compiler automatically passes the
instance to the constructor when you create a new instance of the struct. Note
that in a constructor, you must include the `out` [argument
convention](/docs/manual/values/ownership#argument-conventions) for the `self`
argument. The values of the remaining arguments are assigned to the
corresponding fields of the new instance. (Don't worry about the `var` keyword
and `^` character for now. We'll discuss both of them in more detail later.)

To reduce the amount of boilerplate code you need to write, Mojo provides a
decorator called [`@fieldwise_init`](/docs/reference/decorators/fieldwise-init/)
that automatically generates a constructor for you that performs "field-wise"
initialization. The constructor's arguments have the same names and types as the
struct's fields and appear in the same order. This means that given our original
definition of `Grid`, we can create an instance of `Grid` like this:

```mojo
my_grid = Grid(2, 2, [[0, 1], [1, 1]])
```

We can then access the field values with "dot" syntax like this:

```mojo
print("Rows:", my_grid.rows)
```

```output
Rows: 2
```

However, we also need to be able to copy and move instances of `Grid`—for
example, when we pass an instance of `Grid` to a function or method. Mojo
structs support several different [lifecycle methods](/docs/manual/lifecycle/)
that define the behavior when an instance of the struct is created, moved,
copied, and destroyed.

Structs that conform to the [`Movable`](/docs/std/builtin/value/Movable/)
[trait](/docs/manual/traits/) denote a type whose value can be moved, and
structs that conform to the [`Copyable`](/docs/std/builtin/value/Copyable/)
trait denote a type whose value can be *explicitly* copied and/or moved. You can
then implement custom move and copy constructors that perform the necessary
operations to move or copy the instance.

As a convenience for structs that are basic aggregations of other types and
don't require custom resource management or lifecycle behaviors, you can simply
indicate that the struct conforms to the `Movable` or `Copyable` traits without
implementing the corresponding lifecycle methods. In that case, the Mojo
compiler automatically generates the missing methods for you. For our simple
`Grid` struct, indicating that it conforms to the `Copyable`
trait is enough to have the Mojo compiler automatically generate the missing
methods for us. The `Copyable` trait also provides a default implementation of
the copy initializer and `copy()`, so you don't need to implement it yourself.

:::note

If you define a simple struct where all fields are types that conform to the
`ImplicitlyCopyable` trait—such as `String` and numeric types—you could indicate
that your struct conforms to the `ImplicitlyCopyable` trait instead of the
`Copyable` trait. However, our `Grid` struct uses a `List[List[Int]]` field,
which is not implicitly copyable.

Also see the [Life of a value](/docs/manual/lifecycle/life/) section of the Mojo
Manual for more information about the different lifecycle methods and how to
implement them.

:::

## 7. Import a module and use our custom `Grid` type

Now let's edit `life.mojo` to import `Grid` from our new module and update our
code to use it.

```mojo title="life.mojo"
from gridv1 import Grid

def grid_str(grid: Grid) -> String:
    # Create an empty String
    str = String()

    # Iterate through rows 0 through rows-1
    for row in range(grid.rows):
        # Iterate through columns 0 through cols-1
        for col in range(grid.cols):
            if grid.data[row][col] == 1:
                str += "*"  # If cell is populated, append an asterisk
            else:
                str += " "  # If cell is not populated, append a space
        if row != grid.rows - 1:
            str += "\n"     # Add a newline between rows, but not at the end
    return str

def main():
    glider = [
        [0, 1, 0, 0, 0, 0, 0, 0],
        [0, 0, 1, 0, 0, 0, 0, 0],
        [1, 1, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
    ]
    start = Grid(8, 8, glider^)
    result = grid_str(start)
    print(result)
```

All these changes are straightforward except for the line where we create the
`Grid` instance. Our new `Grid` needs to take *ownership* of the
`List[List[Int]]` representing the grid state. (Technically, the `data` field of
the `Grid` will own the value.) However, the `glider` variable currently owns
the list.

One alternative—if we plan to use the value of the `glider` variable again later
in `main()`—would be to create a copy of the `glider` list to pass to the `Grid`
constructor, like this:

```mojo
    start = Grid(8, 8, glider.copy())
```

In our case, we don't need to use the `glider` variable again later, so we can
instead use the `^` transfer sigil to *transfer ownership* of the list to the
corresponding argument of the `Grid` constructor. After the transfer, the
`glider` variable is uninitialized. You would need to assign a new value to it
if you want to use the variable again. For more information about ownership and
the `^` transfer sigil, see the [Ownership](/docs/manual/values/ownership/)
section of the Mojo manual.

At this point, we've made several changes to improve the structure of our
program, but the output should remain the same.

```bash
mojo life.mojo
```

```output
 *
  *
***

```

## 8. Implement `grid_str()` as a method

Our `grid_str()` function is really a utility function unique to the `Grid`
type. Rather than defining it as a standalone function, it makes more sense
to define it as part of the `Grid` type as a method.

To do so, move the function into `gridv1.mojo` and edit it to look like this (or
simply copy the code below into `gridv1.mojo`):

```mojo title="gridv1.mojo"
@fieldwise_init
struct Grid(Copyable):
    var rows: Int
    var cols: Int
    var data: List[List[Int]]

    def grid_str(self) -> String:
        # Create an empty String
        writer = String()

        # Iterate through rows 0 through rows-1
        for row in range(self.rows):
            # Iterate through columns 0 through cols-1
            for col in range(self.cols):
                if self.data[row][col] == 1:
                    # If cell is populated, write an asterisk
                    writer.write_string("*")
                else:
                    # If cell is not populated, append a space
                    writer.write_string(" ")
            if row != self.rows - 1:
                # Add a newline between rows, but not at the end
                writer.write_string("\n")
        return writer
```

Aside from moving the code from one source file to another, there are a few
other changes we've made:

- The function definition is indented to indicate that it's a method defined by
  the `Grid` struct. This also changes how we invoke the function.
  Instead of `grid_str(my_grid)`, we now write `my_grid.grid_str()`.
- We've changed the argument name to `self`. When you invoke an instance method,
  Mojo automatically passes the instance as the first argument, followed by any
  explicit arguments you provide. Although we could use any name we like
  for this argument, the convention is to call it `self`.
- We've deleted the argument's type annotation. The compiler knows the
  first argument of the method is an instance of the struct, so it doesn't
  require an explicit type annotation.
- We don't need to add an explicit [argument
  convention](/docs/manual/values/ownership#argument-conventions) to the `self`
  argument because we're using it as a read-only reference to the instance,
  which is the default behavior for a method argument.

Now that we've refactored the function into an instance method, we also need to
update the code in `life.mojo` where we invoke it from `main()`:

```mojo title="life.mojo"
from gridv1 import Grid

def main():
    glider = [
        [0, 1, 0, 0, 0, 0, 0, 0],
        [0, 0, 1, 0, 0, 0, 0, 0],
        [1, 1, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
    ]
    start = Grid(8, 8, glider^)
    print(start.grid_str())
```

Once again, our refactoring has improved the structure of our code, but it still
produces the same output. You can verify this by running the program again.

## 9. Implement support for the `Writable` trait

You can convert most Mojo types to `String` using `String(my_val)` to produce a
`String` representation of that instance. However, you'll get an error if you
try to do that with our current implementation of `Grid`. Let's fix that.

Because the Mojo compiler performs static type checking, a `String` constructor
can accept a value only if its type implements some required behavior—in this
case, it only accepts types that can generate a `String` representation.

To enforce this, the `String()` constructors require a type to conform to the
[`Writable`](/docs/std/format/Writable/) trait. (This type of function is
sometimes referred to as a
[*generic* function](/docs/manual/parameters/#parameters-and-generics).) Each
trait requires a conforming type to implement a `write_to()` method that writes
its `String` representation. (To learn more, read
[The `Writable` trait](/docs/manual/traits/#the-writable-trait).)

Notice that `write_to()` takes a `mut writer: Some[Writer]` argument. The
`Writer` trait represents anything that can be written to—`String` buffers,
files, network streams, and so on. By writing to a generic `Writer` instead of
building and returning a `String`, your type works with any output destination.

Our `grid_str()` method already writes our `String` representation, so it looks
like we'll have to rename it to `write_to()` and add the `Writer` argument to
the function, and remove the `String` return value. We also need to indicate
which trait `Grid` conforms to. In our case, it must be `Writable`.

In `gridv1.mojo`, we need to update the `Grid` declaration to indicate that
the type conforms to `Writable` and update the `grid_str()` to `write_to()`.

```mojo title="gridv1.mojo"
@fieldwise_init
struct Grid(Copyable, Writable):
    ...

    def write_to(self, mut writer: Some[Writer]):
      # Iterate through rows 0 through rows-1
      for row in range(self.rows):
          # Iterate through columns 0 through cols-1
          for col in range(self.cols):
              if self.data[row][col] == 1:
                  # If cell is populated, write an asterisk
                  writer.write_string("*")
              else:
                  # If cell is not populated, write a space
                  writer.write_string(" ")
          if row != self.rows - 1:
              # Add a newline between rows, but not at the end
              writer.write_string("\n")
```

Now let's verify that `String()` works with an instance of `Grid`.

```mojo title="life.mojo"
def main():
    ...
    start = Grid(8, 8, glider^)
    print(String(start))
```

If you run the program again, you should still see the same glider pattern as
before.

```bash
mojo life.mojo
```

```output
 *
  *
***

```

## 10. Implement methods to support indexing

Looking at the implementation of `write_to()`, you'll notice that we use
`self.data[row][col]` to retrieve the value of a cell in the grid. If
`my_grid` is an instance of `Grid`, we would use `my_grid.data[row][col]` to
refer to a cell in the grid. This breaks a fundamental principle of
encapsulation because we need to know that `Grid` stores the game state in a
field called `data`, and that field is a `List[List[Int]]`. If we later decide
to change the internal implementation of `Grid`, there could be a lot of
code that would need to be changed.

A cleaner approach is to provide "getter" and "setter" methods to access cell
values. We could simply define methods like `get_cell()` and `set_cell()`, but
this is a good opportunity to show how we can define the behavior of built-in
operators for custom Mojo types. Specifically, we'll implement support for
indexing so we can refer to a cell with syntax like `my_grid[row, col]`.
This will be useful when we implement support for evolving the state of the
grid.

As described in [Operators, expressions, and dunder
methods](/docs/manual/operators), Mojo allows us to define the behavior of many
built-in operators for a custom type by implementing special *dunder*
(double underscore) methods. In the case of indexing, the two methods are
`__getitem__()` and `__setitem__()`. Let's add the following methods to the
`Grid` struct in `gridv1.mojo`:

```mojo title="gridv1.mojo"
@fieldwise_init
struct Grid(Copyable, Writable):
    ...
    def __getitem__(self, row: Int, col: Int) -> Int:
        return self.data[row][col]

    def __setitem__(mut self, row: Int, col: Int, value: Int) -> None:
        self.data[row][col] = value
```

The implementation of `__getitem__()` is straightforward. For the given values
of `row` and `col`, we just need to retrieve and return the corresponding value
from the nested `List[List[Int]]` stored in the `data` field of the instance.

The body of `__setitem__()` is similarly straightforward. We just take the given
`value` and store it in the corresponding `row` and `col` in `data`. One new
thing in the declaration is that we set the return type to `None` to indicate
that the method doesn't have a return value. More notable is that we've added
the `mut` [argument passing
convention](/docs/manual/values/ownership/#argument-passing-conventions) to
the `self` argument to explicitly tell the Mojo compiler that we want to mutate
the state of the current instance. If we were to omit `mut`, we would get an
error because the compiler would default to immutable access for the argument.

Now that we've implemented these methods, we can update `write_to()` to use
indexing syntax to access the cell value.

```mojo title="gridv1.mojo"
@fieldwise_init
struct Grid(Copyable, Writable):
    ...
    def write_to(self, mut writer: Some[Writer]):
        ...
            # Iterate through columns 0 through cols-1
            for col in range(self.cols):
                if self[row, col] == 1:
                    ...
```

<!-- markdownlint-disable MD033 -->
### Click here to see the complete `gridv1.mojo` so far:

<!-- markdownlint-enable MD033 -->
```mojo title="gridv1.mojo"
@fieldwise_init
struct Grid(Copyable, Writable):
var rows: Int
var cols: Int
var data: List[List[Int]]
def write_to(self, mut writer: Some[Writer]):
# Iterate through rows 0 through rows-1
for row in range(self.rows):
# Iterate through columns 0 through cols-1
for col in range(self.cols):
if self[row, col] == 1:
# If cell is populated, write an asterisk
writer.write_string("*")
else:
# If cell is not populated, write a space
writer.write_string(" ")
if row != self.rows - 1:
# Write a newline between rows, but not at the end
writer.write_string("\n")
def __getitem__(self, row: Int, col: Int) -> Int:
return self.data[row][col]
def __setitem__(mut self, row: Int, col: Int, value: Int) -> None:
self.data[row][col] = value
```
<!-- markdownlint-disable MD033 -->
<!-- markdownlint-enable MD033 -->

Our refactoring hasn't changed our program's behavior, but it's still a good
idea to run it to ensure we don't have any errors in our code.

## 11. Define a static method to generate random grids

So far, we've used the glider to build the basic functionality of our `Grid`
type. However, what's much more interesting is to start with a grid in a random
state and see how it evolves over time.

Let's add a *static method* named `random()` to the `Grid` struct to generate
and return an instance of `Grid` with a random state. A static method doesn't
operate on specific instances of the type, so it can be invoked as a utility
function. We indicate that a method is static by using the
`@staticmethod` decorator.

```mojo title="gridv1.mojo"
from std import random

@fieldwise_init
struct Grid(Copyable, Writable):
    ...
    @staticmethod
    def random(rows: Int, cols: Int) -> Self:
        # Seed the random number generator using the current time.
        random.seed()

        var data: List[List[Int]] = []

        for _ in range(rows):
            var row_data: List[Int] = []
            for _ in range(cols):
                # Generate a random 0 or 1 and append it to the row.
                row_data.append(Int(random.random_si64(0, 1)))
            data.append(row_data^)

        return Self(rows, cols, data^)
```

At the top of the file, we're importing the `random` package from the Mojo
standard library. It includes several functions related to random number
generation.

By default, the [pseudorandom number
generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) used by
the Mojo standard library currently uses a fixed seed. This means it generates
the same sequence of numbers unless you provide a different seed, which is
useful for testing purposes. However, for this application, we want to call
`random.seed()` to set a seed value based on the current time, which gives us a
unique value every time.

Then we create `data` as an empty `List[List[Int]]`, which we'll populate with a
random initial state. For each cell, we call
[`random.random_si64()`](/docs/std/random/random/random_si64/), which returns
a random integer value from the provided minimum and maximum values of 0 and 1,
respectively. This function actually returns a value of type `Int64`, which is a
signed 64-bit integer value. As described in [Numeric
types](/docs/manual/types#numeric-types), this is *not* the same as the `Int`
type, whose precision is dependent on the native word size of the system.
Therefore, we're passing this value to the
[`Int()`](/docs/std/builtin/int/Int/#__init__) constructor, which explicitly
converts a numeric value to an `Int`.

After creating a complete row of random values, we append it to `data`. The
`List` in `data` *owns* all its elements, so when we call `append()`, we need
to decide whether to transfer ownership of the new row or provide a copy of it.
In this case, we don't need to use the row again, so we use the `^` transfer
sigil to transfer ownership of the row to the `List` in `data`. (We didn't need
to use the `^` sigil when appending the `Int` values because they're
*implicitly* copyable.)

The return type of the method is `Self`, which is an alias for the type of the
struct. This is a convenient shortcut if the actual name of the struct is long
or includes parameters. The last line uses `Self()` to invoke the struct's
constructor and return a newly created instance with random data. Once again, we
use the `^` transfer sigil to transfer ownership of the newly created
`List[List[Int]]` to the new instance rather than make a copy of it.

:::note

The `for` loops in the code above assign the loop value to "`_`", the [*discard
pattern*](/docs/manual/lifecycle/death#explicit-lifetime-extension), to indicate
that it's intentionally not used. Without this, the Mojo compiler would
report a warning that the loop variable is unused.

:::

Now we can update the `main()` function in `life.mojo` to create a random `Grid`
and print it.

```mojo title="life.mojo"
...

def main():
    start = Grid.random(8, 16)
    print(String(start))
```

Run the program a few times to verify that it generates a different grid each
time.

```bash
mojo life.mojo
```

```output
*** *      ****
*  ****   ******
* * *****
*  * ** **
 *    * ** ****
* **  * * * ***
 * * **  **  **
  * ***** **
```

## 12. Implement a method to evolve the grid

It's finally time to let our world evolve. We'll implement an `evolve()` method
to calculate the state of the grid for the next generation. One option would be
to do an in-place modification of the existing `Grid` instance. Instead,
we'll have `evolve()` return a new instance of `Grid` for the next generation.

```mojo title="gridv1.mojo"
...
struct Grid(Copyable, Writable):
    ...
    def evolve(self) -> Self:
        next_generation = List[List[Int]]()

        for row in range(self.rows):
            row_data = List[Int]()

            # Calculate neighboring row indices, handling "wrap-around"
            row_above = (row - 1) % self.rows
            row_below = (row + 1) % self.rows

            for col in range(self.cols):
                # Calculate neighboring column indices, handling "wrap-around"
                col_left = (col - 1) % self.cols
                col_right = (col + 1) % self.cols

                # Determine number of populated cells around the current cell
                num_neighbors = (
                    self[row_above, col_left]
                    + self[row_above, col]
                    + self[row_above, col_right]
                    + self[row, col_left]
                    + self[row, col_right]
                    + self[row_below, col_left]
                    + self[row_below, col]
                    + self[row_below, col_right]
                )

                # Determine the state of the current cell for the next generation
                new_state = 0
                if self[row, col] == 1 and (
                    num_neighbors == 2 or num_neighbors == 3
                ):
                    new_state = 1
                elif self[row, col] == 0 and num_neighbors == 3:
                    new_state = 1
                row_data.append(new_state)

            next_generation.append(row_data^)

        return Self(self.rows, self.cols, next_generation^)
```

We start with an empty `List[List[Int]]` to represent the state of the next
generation. Then we use nested `for` loops to iterate over each row and column
of the existing `Grid` to determine the state of each cell in the next
generation.

For each cell in the grid, we need to count the number of populated neighboring
cells. Because we're modeling the world as a toroid, we need to consider the top
and bottom rows as adjacent and the leftmost and rightmost columns as
adjacent. As we iterate through each row and column, we're using the modulo
operator (`%`) to handle "wrap-around" when we calculate the indices of the rows
above and below and the columns to the left and right of the current cell. (For
example, if there are 8 rows, then `-1 % 8` is 7.)

Then we apply the Game of Life rules that determine whether the current cell is
populated (1) or unpopulated (0) for the next generation:

- A populated cell with either 2 or 3 populated neighbors remains populated in
  the next generation
- An unpopulated cell with exactly 3 populated neighbors becomes populated in
  the next generation
- All other cells become unpopulated in the next generation

After calculating the state of the next generation, we use `Self()` to create a
new instance of `Grid`, and return the newly created instance.

Now that we can evolve the grid, let's use it in `life.mojo`. We'll add a
`run_display()` function to control the game's main loop:

- Display the current `Grid`
- Prompt the user to continue or quit
- Break out of the loop if the user enters `q`
- Otherwise, calculate the next generation and loop again

Notice that `run_display()` declares `raises` because it calls `input()`, which
can raise an error. Since `main()` calls `run_display()` without handling the
error, it also needs `raises`.

Also note that `run_display()` declares the `grid` argument with the `var`
argument convention to take ownership of the `Grid` instance. If we used
the default argument convention instead, `grid` would be an immutable
reference binding to the original `Grid` instance. In that case, we'd get a
compile-time error when we tried to assign the result of `grid.evolve()` to
`grid` because Mojo doesn't allow you to re-bind a reference to a different
value. See [Reference bindings](/docs/manual/variables/#reference-bindings)
for more information.

Then we'll update `main()` to create a random initial `Grid` and pass it to
`run_display()`, transferring ownership with the `^` sigil. Here's
the updated version of `life.mojo`:

```mojo title="life.mojo"
from gridv1 import Grid

def run_display(var grid: Grid) raises -> None:
    while True:
        print(String(grid))
        print()
        if input("Enter 'q' to quit or press 

  

  
</ListingCards>
<!-- markdownlint-enable MD033 -->

:::tip

To use AI coding assistants with Mojo, see our guide on
[using Mojo AI skills](/docs/tools/skills/).

:::
