---
title: Rust handbook
sidebar_label: Handbook
toc_max_heading_level: 6
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Utilizing Rust in a monorepo is a trivial task, thanks to Cargo, and also moon. With this handbook,
we'll help guide you through this process.

:::info

moon is not a build system and does _not_ replace Cargo. Instead, moon runs `cargo` commands, and
efficiently orchestrates those tasks within the workspace.

:::

## moon setup

For this part of the handbook, we'll be focusing on [moon](/moon), our task runner. To start,
languages in moon act like plugins, where their functionality and support _is not_ enabled unless
explicitly configured. We follow this approach to avoid unnecessary overhead.

### Enabling the language

To enable Rust, define the [`rust`](../../config/toolchain#rust) setting in
[`.moon/toolchains.*`](../../config/toolchain), even if an empty object.

```yaml title=".moon/toolchains.yml"
# Enable Rust
rust: {}

# Enable Rust and override default settings
rust:
  syncToolchainConfig: true
```

Or by pinning a `rust` version in [`.prototools`](../../proto/config) in the workspace root.

```toml title=".prototools"
rust = "1.93.0"
```

This will enable the Rust toolchain and provide the following automations around its ecosystem:

- Manifests and lockfiles are parsed for accurate dependency versions for hashing purposes.
- Cargo binaries (in `~/.cargo/bin`) are properly located and executed.
- Automatically sync `rust-toolchain.toml` configuration files.
- For non-workspaces, will inherit `package.name` from `Cargo.toml` as a project alias.
- And more to come!

### Utilizing the toolchain

When a language is enabled, moon by default will assume that the language's binary is available
within the current environment (typically on `PATH`). This has the downside of requiring all
developers and machines to manually install the correct version of the language, _and to stay in
sync_.

Instead, you can utilize [moon's toolchain](../../concepts/toolchain), which will download and
install the language in the background, and ensure every task is executed using the exact version
across all machines.

Enabling the toolchain is as simple as defining the
[`rust.version`](../../config/toolchain#version-2) setting.

```yaml title=".moon/toolchains.yml"
# Enable Rust toolchain with an explicit version
rust:
  version: '1.69.0'
```

> Versions can also be defined with [`.prototools`](../../proto/config).

:::caution

moon requires `rustup` to exist in the environment, and will use this to install the necessary Rust
toolchains. moon will attempt to auto-install `rustup` if it's not found, but this may fail in some
environments.

:::

## Repository structure

Rust/Cargo repositories come in two flavors: a single crate with one `Cargo.toml`, or multiple
crates with many `Cargo.toml`s using
[Cargo workspaces](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html). The latter is
highly preferred as it enables Cargo incremental caching.

With moon, you can place [`moon.*`](../../config/project) in each crate, or once relative to
`Cargo.lock`. The choice is yours! But be aware, if you do per-crate, Cargo itself will "wait for
build lock" when multiple processes are running.

An example of this layout is demonstrated below:

<Tabs
  groupId="repo-layout"
  defaultValue="workspaces"
  values={[
    { label: 'Workspaces', value: 'workspaces' },
    { label: 'Workspaces (per-crate)', value: 'workspaces-many' },
    { label: 'Non-workspaces', value: 'project' },
  ]}
>
<TabItem value="workspaces">

```
/
├── .moon/
├── crates/
│   ├── client/
|   │   ├── ...
│   │   └── Cargo.toml
│   ├── server/
|   │   ├── ...
│   │   └── Cargo.toml
│   └── utils/
|       ├── ...
│       └── Cargo.toml
├── target/
├── Cargo.lock
├── Cargo.toml
└── moon.yml
```

</TabItem>
<TabItem value="workspaces-many">

```
/
├── .moon/
├── crates/
│   ├── client/
|   │   ├── ...
|   │   ├── moon.yml
│   │   └── Cargo.toml
│   ├── server/
|   │   ├── ...
|   │   ├── moon.yml
│   │   └── Cargo.toml
│   └── utils/
|       ├── ...
|   │   ├── moon.yml
│       └── Cargo.toml
├── target/
├── Cargo.lock
└── Cargo.toml
```

</TabItem>
<TabItem value="project">

```
/
├── .moon/
├── src/
│   └── lib.rs
├── tests/
│   └── ...
├── target/
├── Cargo.lock
├── Cargo.toml
└── moon.yml
```

</TabItem>
</Tabs>

### Example `moon.*`

The following configuration represents a base that covers most Rust projects.

<Tabs
  groupId="repo-layout"
  defaultValue="workspaces"
  values={[
    { label: 'Workspaces', value: 'workspaces' },
    { label: 'Workspaces (per-crate) / Non-workspaces', value: 'project' },
  ]}
>
<TabItem value="workspaces">

```yaml title="<project>/moon.yml"
language: 'rust'
layer: 'application'

env:
  CARGO_TERM_COLOR: 'always'

fileGroups:
  sources:
    - 'crates/*/src/**/*'
    - 'crates/*/Cargo.toml'
    - 'Cargo.toml'
  tests:
    - 'crates/*/benches/**/*'
    - 'crates/*/tests/**/*'

tasks:
  build:
    command: 'cargo build'
    inputs:
      - '@globs(sources)'
  check:
    command: 'cargo check --workspace'
    inputs:
      - '@globs(sources)'
  format:
    command: 'cargo fmt --all --check'
    inputs:
      - '@globs(sources)'
      - '@globs(tests)'
  lint:
    command: 'cargo clippy --workspace'
    inputs:
      - '@globs(sources)'
      - '@globs(tests)'
  test:
    command: 'cargo test --workspace'
    inputs:
      - '@globs(sources)'
      - '@globs(tests)'
```

</TabItem>
<TabItem value="project">

```yaml title="<project>/moon.yml"
language: 'rust'
layer: 'application'

env:
  CARGO_TERM_COLOR: 'always'

fileGroups:
  sources:
    - 'src/**/*'
    - 'Cargo.toml'
  tests:
    - 'benches/**/*'
    - 'tests/**/*'

tasks:
  build:
    command: 'cargo build'
    inputs:
      - '@globs(sources)'
  check:
    command: 'cargo check'
    inputs:
      - '@globs(sources)'
  format:
    command: 'cargo fmt --check'
    inputs:
      - '@globs(sources)'
      - '@globs(tests)'
  lint:
    command: 'cargo clippy'
    inputs:
      - '@globs(sources)'
      - '@globs(tests)'
  test:
    command: 'cargo test'
    inputs:
      - '@globs(sources)'
      - '@globs(tests)'
```

</TabItem>
</Tabs>

## Cargo integration

You can't use Rust without Cargo -- well you could but why would you do that? With moon, we're doing
our best to integrate with Cargo as much as possible. Here's a few of the benefits we currently
provide.

### Global binaries

Cargo supports global binaries through the
[`cargo install`](https://doc.rust-lang.org/cargo/commands/cargo-install.html) command, which
installs a crate to `~/.cargo/bin`, or makes it available through the `cargo <crate>` command. These
are extremely beneficial for development, but they do require every developer to manually install
the crate (and appropriate version) to their machine.

With moon, this is no longer an issue with the [`rust.bins`](../../config/toolchain#bins) setting.
This setting requires a list of crates (with optional versions) to install, and moon will install
them as part of the task runner install dependencies action. Furthermore, binaries will be installed
with [`cargo-binstall`](https://crates.io/crates/cargo-binstall) in an effort to reduce build and
compilation times.

```yaml title=".moon/toolchains.yml" {2-4}
rust:
  bins:
    - 'cargo-make@0.35.0'
    - 'cargo-nextest'
```

At this point, tasks can be configured to run this binary as a command. The `cargo` prefix is
optional, as we'll inject it when necessary.

```yaml title="<project>/moon.yml"
tasks:
  test:
    command: 'nextest run --workspace'
    toolchain: 'rust'
```

:::tip

The `cargo-binstall` crate may require a `GITHUB_TOKEN` environment variable to make GitHub Releases
API requests, especially in CI. If you're being rate limited, or fail to find a download, try
creating a token with necessary permissions.

:::

### Lockfile handling

To expand our integration even further, we also take `Cargo.lock` into account, and apply the
following automations when a target is being ran:

- If the lockfile does not exist, we generate one with
  [`cargo generate-lockfile`](https://doc.rust-lang.org/cargo/commands/cargo-generate-lockfile.html).
- We parse and extract the resolved checksums and versions for more accurate hashing.

## FAQ

### Should we cache the `target` directory as an output?

No, definitely not! Both moon and Cargo support incremental caching, but they're not entirely
compatible, and will most likely cause problems when used together.

The biggest factor is that moon's caching and hydration uses a tarball strategy, where each task
would unpack a tarball on cache hit, and archive a tarball on cache miss. The Cargo target directory
is extremely large (moon's is around 50gb), and coupling this with our tarball strategy is not
viable. This would cause massive performance degradation.

However, at maximum, you _could_ cache the compiled binary itself as an output, instead of the
entire target directory. Example:

```yaml title="moon.yml"
tasks:
  build:
    command: 'cargo build --release'
    outputs: ['target/release/moon']
```

### How can we improve CI times?

Rust is known for slow build times and CI is no exception. With that being said, there are a few
patterns to help alleviate this, both on the moon side and outside of it.

To start, you can cache Rust builds in CI. This is a non-moon solution to the `target` directory
problem above.

1. If you use GitHub Actions, feel free to use our
   [moonrepo/setup-rust](https://github.com/moonrepo/setup-rust) action, which has built-in caching.
2. A more integrated solution is [sccache](https://crates.io/crates/sccache), which stores build
   artifacts in a cloud storage provider.
