RFC 013: Rust Crate Dependencies¶
Status: Draft
Created: 2025-12-16
Supersedes: Parts of RFC 005 (Cargo integration section)
Summary¶
Define a comprehensive system for specifying Rust crate dependencies in Incan, including inline version annotations,
project configuration (incan.toml), and lock files for reproducibility.
Motivation¶
Incan compiles to Rust, meaning access to the Rust ecosystem is a core value proposition. The current implementation has limitations:
- Known-good crates work - common crates like
serde,tokiohave curated defaults - Unknown crates error - no user-facing way to specify version/features
- Workaround is manual - edit generated
Cargo.toml(clunky, not reproducible)
This RFC introduces a proper dependency management system that is:
- Easy by default - common crates "just work"
- Flexible when needed - any crate can be used with explicit config
- Reproducible - builds are deterministic via lock files
- Pythonic -
incan.tomlfeels likepyproject.toml
Goals¶
- Allow any Rust crate with explicit version specification
- Maintain known-good defaults as convenient fallbacks
- Support features, git sources, and path dependencies
- Provide a familiar project configuration format
- Enable reproducible builds via lock file
Non-Goals (this RFC)¶
- Automatic version resolution/upgrade (future:
incan update) - Private registry support
- Workspace/multi-project configuration
- Native FFI bindings beyond Rust
1. Inline Version Annotations¶
1.1 Basic Version¶
# Pin a specific version
import rust::my_crate @ "1.0"
from rust::obscure_lib @ "0.5" import Widget
# Semver operators (like Cargo)
import rust::some_crate @ "^1.2" # >=1.2.0, <2.0.0
import rust::other_crate @ "~1.2" # >=1.2.0, <1.3.0
import rust::exact_crate @ "=1.2.3" # exactly 1.2.3
1.2 Version with Features¶
# Enable features
import rust::tokio @ "1.0" with ["full"]
import rust::serde @ "1.0" with ["derive", "rc"]
# Import from crate with features
from rust::sqlx @ "0.7" with ["runtime-tokio", "postgres"] import Pool
1.3 Grammar Extension¶
(* Extended import syntax for rust:: crates *)
rust_import = "import" "rust" "::" crate_path [ version_spec ] [ "as" IDENT ]
| "from" "rust" "::" crate_path [ version_spec ] "import" import_list ;
crate_path = IDENT { "::" IDENT } ;
version_spec = "@" version_string [ "with" feature_list ] ;
version_string = STRING ; (* "1.0", "^1.2", "~0.5", "=1.2.3" *)
feature_list = "[" STRING { "," STRING } "]" ;
import_list = IDENT { "," IDENT } ;
2. Project Configuration (incan.toml)¶
The incan.toml format is inspired by Python's pyproject.toml - familiar, readable, and declarative.
2.1 Minimal Example¶
[project]
name = "my_app"
version = "0.1.0"
2.2 Full Example¶
[project]
name = "my_app"
version = "0.1.0"
description = "An example Incan application"
authors = ["Alice <alice@example.com>"]
license = "MIT"
readme = "README.md"
# Minimum Incan version required
requires-incan = ">=0.2.0"
# Entry point for `incan run`
[project.scripts]
main = "src/main.incn"
# Rust crate dependencies
[rust.dependencies]
# Simple version string
reqwest = "0.12"
rand = "0.8"
# Version with features
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "mysql"] }
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
# Git dependency
my_internal_crate = { git = "https://github.com/company/crate", tag = "v1.0.0" }
# Path dependency (for local development)
local_lib = { path = "../my-local-lib" }
# Optional dependencies (enabled via features)
[rust.dependencies.optional]
fancy_logging = { version = "0.3", optional = true }
# Development dependencies (not included in release builds)
[rust.dev-dependencies]
criterion = "0.5"
proptest = "1.0"
# Build configuration
[build]
# Rust edition for generated code
rust-edition = "2021"
# Optimization level: "debug", "release", "release-lto"
profile = "release"
# Target triple (optional, defaults to host)
# target = "x86_64-unknown-linux-gnu"
# Feature flags for the Incan project itself
[project.features]
default = ["json"]
json = [] # Enables JSON serialization support
full = ["json", "fancy_logging"]
2.3 Section Reference¶
| Section | Description |
|---|---|
[project] |
Project metadata (name, version, authors, etc.) |
[project.scripts] |
Entry points for CLI commands |
[project.features] |
Optional feature flags |
[rust.dependencies] |
Rust crate dependencies |
[rust.dev-dependencies] |
Development-only dependencies |
[build] |
Build configuration options |
2.4 Dependency Specification Formats¶
[rust.dependencies]
# String shorthand - just version
crate_a = "1.0"
# Table form - version + features
crate_b = { version = "1.0", features = ["foo", "bar"] }
# Table form - git source
crate_c = { git = "https://github.com/...", branch = "main" }
crate_d = { git = "https://github.com/...", tag = "v1.0.0" }
crate_e = { git = "https://github.com/...", rev = "abc1234" }
# Table form - path source
crate_f = { path = "../local-crate" }
# Optional dependency
crate_g = { version = "1.0", optional = true }
# Default features disabled
crate_h = { version = "1.0", default-features = false, features = ["only-this"] }
3. Lock File (incan.lock)¶
For reproducible builds, Incan generates a lock file capturing exact resolved versions.
3.1 Format¶
# Auto-generated by Incan - do not edit manually
# Regenerate with: incan lock
[metadata]
incan-version = "0.2.0"
generated = "2025-12-16T10:30:00Z"
[[rust.package]]
name = "tokio"
version = "1.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abc123def456..."
features = ["full"]
[[rust.package]]
name = "serde"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "def789abc012..."
features = ["derive"]
[[rust.package]]
name = "my_internal_crate"
version = "1.0.0"
source = "git+https://github.com/company/crate?tag=v1.0.0#commit-sha"
3.2 CLI Commands¶
| Command | Description |
|---|---|
incan build |
Build; uses lock file if present, creates if not |
incan lock |
Regenerate lock file from current dependencies |
incan update |
Update dependencies to latest compatible versions |
incan update <crate> |
Update specific crate only |
4. Resolution Rules¶
When resolving a Rust crate dependency, the following precedence applies (highest to lowest):
1. incan.toml [rust.dependencies] → explicit project config wins
2. Inline version annotation → import rust::foo @ "1.0"
3. Known-good defaults → curated list in compiler
4. Error → unknown crate without version
4.1 Known-Good Defaults¶
The compiler maintains a curated list of common crates with tested version/feature combinations. These serve as convenient defaults, not restrictions:
// In compiler (simplified)
static KNOWN_GOOD: &[(&str, &str)] = &[
("serde", r#"{ version = "1.0", features = ["derive"] }"#),
("tokio", r#"{ version = "1", features = ["rt-multi-thread", "macros"] }"#),
("reqwest", r#"{ version = "0.11", features = ["json"] }"#),
// ... etc
];
Users can override these defaults via incan.toml or inline annotations.
4.2 Conflict Resolution¶
# This is an error - conflicting versions
import rust::tokio @ "1.0" # Inline says 1.0
# incan.toml says tokio = "2.0"
Error message:
error: conflicting versions for `tokio`
--> src/main.incn:3
import rust::tokio @ "1.0"
--> incan.toml:12
tokio = "2.0"
Remove the inline version to use incan.toml, or update incan.toml to match.
5. Error Messages¶
5.1 Unknown Crate Without Version¶
error: unknown Rust crate `my_obscure_lib`
--> src/main.incn:5
import rust::my_obscure_lib
This crate isn't in the known-good list. Specify a version:
import rust::my_obscure_lib @ "1.0"
Or add it to incan.toml:
[rust.dependencies]
my_obscure_lib = "1.0"
Tip: Check https://crates.io/crates/my_obscure_lib for available versions.
5.2 Feature Not Found¶
error: feature `nonexistent` not found in crate `tokio`
--> src/main.incn:3
import rust::tokio @ "1.0" with ["nonexistent"]
Available features: full, rt, rt-multi-thread, macros, time, sync, net, ...
5.3 Version Not Found¶
error: version `99.0` of `serde` does not exist
--> src/main.incn:3
import rust::serde @ "99.0"
Latest version: 1.0.195
Tip: Check https://crates.io/crates/serde/versions for available versions.
6. Implementation Phases¶
Phase 1: Inline Versions (Minimal Viable)¶
- Add
@ "version"syntax to parser (implemented incrates/incan_syntax) - Pass version to
ProjectGenerator - Fall back to
*with warning for unknown crates without version (temporary)
Phase 2: Features Support¶
- Add
with ["feature"]syntax (implemented incrates/incan_syntax) - Update codegen to emit features in Cargo.toml
Phase 3: Project Configuration¶
- Parse
incan.tomlif present - Merge with inline annotations per resolution rules
- Add
incan initto create starterincan.toml
Phase 4: Lock File¶
- Generate
incan.lockon first build - Use locked versions on subsequent builds
- Add
incan lock,incan updatecommands
Phase 5: Advanced Sources¶
- Git dependencies (
git = "...") - Path dependencies (
path = "...") - Optional dependencies
7. Examples¶
7.1 Simple Usage (Phase 1+)¶
# Known-good crate - just works
import rust::serde
# Unknown crate - must specify version
import rust::obscure_parser @ "2.1"
# Known-good with different version
import rust::tokio @ "1.35" # Override default
def main() -> None:
print("Hello, Rust ecosystem!")
7.2 Full Project (Phase 3+)¶
incan.toml:
[project]
name = "web_service"
version = "0.1.0"
[rust.dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }
tracing = "0.1"
src/main.incn:
# No version needed - defined in incan.toml
from rust::axum import Router, Json
from rust::sqlx import PgPool
import rust::tracing
async def main() -> None:
tracing::info("Starting server...")
app = Router::new()
# ...
7.3 Mixed Inline and Config¶
incan.toml:
[project]
name = "mixed_example"
version = "0.1.0"
[rust.dependencies]
serde = { version = "1.0", features = ["derive"] }
src/main.incn:
# From incan.toml
import rust::serde
# Inline - not in incan.toml
import rust::uuid @ "1.0" with ["v4"]
# Override known-good default for one-off use
from rust::chrono @ "0.4" with ["serde", "clock"] import DateTime, Utc
8. Comparison with pyproject.toml¶
| pyproject.toml | incan.toml | Notes |
|---|---|---|
[project] |
[project] |
Same structure |
dependencies = [...] |
[rust.dependencies] |
Namespaced for Rust |
[project.optional-dependencies] |
[rust.dependencies.optional] |
Similar |
[tool.pytest] |
N/A | Tool-specific config not in scope |
requires-python |
requires-incan |
Minimum version |
9. Open Questions¶
- Workspace support: Should
incan.tomlsupport workspaces with multiple packages? - Private registries: How to authenticate with private Cargo registries?
- Version ranges in toml: Allow
tokio = ">=1.30, <2.0"or require Cargo syntax? - Auto-update policy: Should
incan updaterespect semver or allow major updates?
10. Checklist¶
Implementing Phase 1: Inline Versions¶
- [ ] Parser:
@ "version"syntax - [ ] Codegen: pass version to ProjectGenerator
- [ ] Error: unknown crate without version (warning +
*fallback) - [ ] Docs: update rust_interop.md
Implementing Phase 2: Features¶
- [ ] Parser:
with ["features"]syntax - [ ] Codegen: emit features in Cargo.toml
- [ ] Error: unknown feature
Implementing Phase 3: Project Configuration¶
- [ ] Parser:
incan.tomlformat - [ ] CLI:
incan initcommand - [ ] Resolution: merge inline + toml + defaults
- [ ] Error: version conflicts
Implementing Phase 4: Lock File¶
- [ ] Generate
incan.lock - [ ] CLI:
incan lockcommand - [ ] CLI:
incan updatecommand - [ ] Build: use lock file when present
Implementing Phase 5: Advanced Sources¶
- [ ] Git dependencies
- [ ] Path dependencies
- [ ] Optional dependencies
- [ ] Dev dependencies