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.tomlentry 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¶
- Project configuration reference - Full
incan.tomlformat - Rust interop - Inline version/feature syntax
- CLI reference -
incan init,incan lock, and flags - CI & automation - Locked builds in CI