# One source tree, two Mojo versions

> Mojo 1.0 and nightly will diverge over time. Here is one possible pattern on how to handle that.

2026-09-08 · [threads.mojo](https://github.com/magmalake/threads.mojo)

Mojo 1.0 and nightly will diverge over time. Here is what you could do about it.

## The pattern

Put the divergence in a type alias, in its own file, one file per toolchain, and
pick between them with `-I`.

```mojo
# compat/stable/threads_compat.mojo
from std.atomic import Atomic
comptime Cell = Atomic[DType.int64]
```

```mojo
# compat/nightly/threads_compat.mojo
from std.atomic import Atomic
comptime Cell = Atomic[Int64]
```

Everything else imports `Cell` and is written once:

```mojo
from threads_compat import Cell
from std.atomic import Ordering


def atomic_fetch_add(ptr: CellPtr, delta: Int64) -> Int64:
    return ptr[].fetch_add[ordering = Ordering.SEQUENTIAL](delta)
```

Then the build names a directory:

```sh
mojo build -I compat/stable  -I src …    # Mojo 1.0.0
mojo build -I compat/nightly -I src …    # nightly
```

In pixi, set it per feature so no task has to know which toolchain it is on:

```toml
[feature.stable.activation.env]
THREADS_COMPAT = "compat/stable"

[feature.nightly.activation.env]
THREADS_COMPAT = "compat/nightly"
```

## Why it collapses to one line

In the general case you may have more than one divergence. Here `std.atomic.Atomic` takes a `DType` on 1.0 and a type on nightly:

| toolchain | compiles | rejected |
| --- | --- | --- |
| Mojo 1.0.0 | `Atomic[DType.int64]` | `Atomic[Int64]` |
| nightly | `Atomic[Int64]` | `Atomic[DType.int64]` |

That looks like it should infect every call site. It does not. Once the alias
exists, `fetch_add[ordering = …]`, `load[ordering = …]` and `store[ordering = …]`
are **identical text** on both toolchains — the difference lives entirely in the
declaration. In `threads.mojo` that leaves one divergent line per toolchain and
400 shared ones.

## Two things that do not work

**A conditional alias at module scope is rejected.** This is the obvious first
attempt, and it fails identically on Mojo 1.0.0 and on nightly:

```mojo
comptime if NIGHTLY:
    comptime Cell = Atomic[Int64]
else:
    comptime Cell = Atomic[DType.int64]
```

```
error: 'comptime if' must be contained in a function
```

That is a scope rule with a message written for it, not a parser tripping over
the syntax — which makes it a thing that could simply be allowed. Filed as
[modular/modular#7096](https://github.com/modular/modular/issues/7096), asking
for `comptime if` at module scope when its branches contain only declarations.

**`-D` defines do not route around it.** The define itself is fine, and further
along than you might expect — `comptime NIGHTLY = get_defined_bool["MOJO_NIGHTLY",
False]()` evaluates happily at module scope, and `mojo build -D MOJO_NIGHTLY=true`
flips it. It is the *branch* that has to live inside a function. A define can
change a value; it cannot choose what a type alias binds to. Useful for
behavioural differences; no help here.

Which leaves the include path, and the include path is enough.

## Keep the mechanism dumb

The temptation is to build an abstraction over "toolchain differences." Resist it.
What is described above is file selection and nothing more: two files, same module
name, one on the path at a time. It does not know _why_ the files differ.

That matters because the next divergence will not look like this one. This case was
one API spelled two ways. When first-class async lands, the difference will be that
a module does not exist on 1.0 at all — and a mechanism built to paper over spellings
would not survive that, while one that swaps files will.

## What it costs a consumer

Nothing, if they take the published tin: it ships precompiled, and the choice was
made when it was built.

Consumers building from **source paths** add one directory to their include list
beside the source one. That is worth stating in your changelog under its own
heading, because it is a build-time break for exactly the people who will not
read past the summary.

***

The pattern is in
[threads.mojo 0.5.0](https://github.com/magmalake/threads.mojo) — `compat/`
alongside `src/`, and `src/threads/atomic.mojo` written once against `Cell`.
Both toolchains run the same 44 tests.
