Skip to content

Managing dependencies

This guide covers how to add, configure, and lock Rust crate dependencies in Incan projects.

For the full manifest format, see: Project configuration reference.
For inline import syntax, see: Rust interop.

Adding a Rust crate (quick start)

The simplest way to use a Rust crate is with an inline version annotation:

import rust::my_crate @ "1.0"

This works in any .incn file, no configuration files needed. The compiler adds the dependency to the generated Cargo.toml automatically.

For common crates (serde, tokio, reqwest, etc.), you don't even need a version — the compiler has tested defaults:

import rust::serde_json as json    # Uses known-good default: serde_json 1.0
import rust::tokio                 # Uses known-good default: tokio 1 with common features

Using incan.toml for project dependencies

For projects with more than a handful of dependencies, create an incan.toml manifest:

incan init

This creates a starter incan.toml. Then declare your dependencies:

[project]
name = "my_app"

[rust-dependencies]
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Once a crate is in incan.toml, the manifest is the single source of truth. Inline @ "version" annotations for that crate are not allowed — use bare imports instead:

# Good: bare import, version comes from incan.toml
import rust::tokio

# Error: inline annotation conflicts with incan.toml
import rust::tokio @ "2.0"

Specifying features

Inline

import rust::tokio @ "1.0" with ["full"]
import rust::serde @ "1.0" with ["derive", "rc"]

When multiple files import the same crate, features are unioned automatically.

In incan.toml

[rust-dependencies]
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

To disable default features:

[rust-dependencies]
serde = { version = "1.0", default-features = false, features = ["derive"] }

Dev-only dependencies

Use [rust-dev-dependencies] for crates needed only during testing:

[rust-dev-dependencies]
criterion = "0.5"
test_helpers = { path = "../test-helpers" }

Dev dependencies are only available in test contexts (files under tests/). Importing a dev-only crate from production code produces a compile-time error.

Locking dependencies

Generating the lock file

Run incan lock to resolve all dependencies and create incan.lock:

incan lock src/main.incn

Or, if your incan.toml has [project.scripts].main set:

incan lock

incan.lock embeds the resolved Cargo.lock and a fingerprint of your dependency inputs. Commit it to version control for reproducible builds.

Default build/test behavior

If incan.lock doesn't exist and you run incan build or incan test without strict flags, the lock file is created automatically on first build.

If incan.lock already exists but is stale, default incan build and incan test warn and reuse the existing embedded Cargo.lock payload without rewriting incan.lock. Run incan lock when you intentionally want to refresh the committed lock file.

For incan test, a generated or changed lock can make the generated Rust harness stale. The runner preheats stale harnesses before executing tests so later test commands can reuse the compiled Cargo state. When lock generation sees Rust dependency inputs or stdlib feature requirements, it also preheats those dependencies with cargo test --no-run into the generated test target domain before writing incan.lock; unchanged relocks reuse a dependency preheat fingerprint.

Strict mode for CI

Use --locked or --frozen to enforce that the lock file exists and is up to date. Use --offline when Cargo must fail instead of touching the network:

# Requires incan.lock to exist and match current deps
incan build src/main.incn --locked

# Disallow network access during Cargo subprocesses
incan build src/main.incn --offline

# Same as --offline plus --locked
incan build src/main.incn --frozen

Before relying on --frozen in a restricted or offline environment, run:

incan tools doctor

incan tools doctor is the supported preflight path for local offline-readiness diagnostics. Its report is advisory, not a guarantee: --frozen still asks Cargo to use offline/locked policy, so any crate source that is missing from Cargo's local inputs can still make the build fail.

If the lock file is missing or stale, the command fails with a clear message:

error: incan.lock is out of date; run `incan lock`

CI can set the same policy with environment variables:

INCAN_LOCKED=1 incan build src/main.incn
INCAN_FROZEN=1 incan test tests/

INCAN_FROZEN=1 implies both offline and locked policy. Use --no-offline, --no-locked, or --no-frozen to disable matching environment defaults for a single command.

Resolution rules

When the compiler resolves a dependency, it follows this precedence:

Priority Source Example
1 (high) incan.toml [dependencies] tokio = "1.35"
2 Inline annotation import rust::tokio @ "1.35"
3 Known-good default import rust::tokio (compiler default)
4 (low) Error import rust::unknown_crate (no version)

Key rules:

  • If a crate is in incan.toml, inline annotations for that crate are forbidden.
  • If the same crate is imported inline in multiple files, the version must match exactly; features are unioned automatically.
  • Known-good defaults only apply when there is no incan.toml entry and no inline annotation.

Cargo feature flags

You can pass Cargo feature flags through the Incan CLI:

# Enable specific features
incan build src/main.incn --cargo-features fancy_logging,metrics

# Disable default features
incan build src/main.incn --cargo-no-default-features

# Enable all features
incan build src/main.incn --cargo-all-features

These flags affect dependency resolution and are included in the lock file fingerprint.

For advanced Cargo-only flags, use --cargo-args or put Cargo arguments after --:

incan build src/main.incn --cargo-args "--timings"
incan test tests/ -- --timings

INCAN_CARGO_ARGS is also supported for simple whitespace-separated CI defaults. Quoting is not parsed inside the environment variable; use the CLI form for arguments containing spaces.

Common errors and fixes

Unknown crate without version

error: unknown Rust crate `my_crate`: no version specified

Fix: Add @ "version" to the import, or add the crate to incan.toml.

Inline annotation conflicts with manifest

error: inline Rust dependency annotation for `tokio` is not allowed because it is configured in incan.toml

Fix: Remove the @ "..." and with [...] from the import. Use incan.toml to control the version.

Version conflict across files

error: conflicting inline dependency specifications for `uuid`

Fix: Make all inline version annotations match, or centralize the dependency in incan.toml.

Dev-only crate in production code

error: Rust crate `criterion` is dev-only and cannot be imported from production code

Fix: Move the crate to [dependencies], or move the import to a test file.

Optional dependency not enabled

error: Rust crate `fancy_logging` is optional but not enabled for this build

Fix: Enable it with --cargo-features fancy_logging, or remove the optional flag.

Stale lock file

error: incan.lock is out of date; run `incan lock`

Fix: Run incan lock to regenerate the lock file after changing dependencies.

See also