Rust Interoperability¶
Incan compiles to Rust, which means you can import from Rust crates and interoperate with Rust types.
Importing Rust Crates¶
Use the rust:: prefix to import from Rust crates:
# Import entire crate
import rust::serde_json as json
# Import specific items
from rust::time import Instant, Duration
from rust::std::collections import HashMap, HashSet
# Import nested items
import rust::serde_json::Value
Automatic Dependency Management¶
When you use import rust::crate_name, Incan automatically adds the dependency to your generated Cargo.toml.
For the bigger picture, see: Projects today.
Strict Dependency Policy¶
Incan uses a strict dependency policy to reduce dependency drift and keep builds predictable:
- Known-good crates: Curated crates have tested version/feature combinations (see table below)
- Unknown crates: Currently produce a compiler error (planned: explicit versions and project config)
This policy prevents "works on my machine" issues caused by wildcard (*) dependencies
that resolve to different versions at different times.
Known-Good Crates¶
The following crates have pre-configured versions with appropriate features:
| Crate | Version | Features |
|---|---|---|
| serde | 1.0 | derive |
| serde_json | 1.0 | - |
| tokio | 1 | rt-multi-thread, macros, time, sync |
| time | 0.3 | formatting, macros |
| chrono | 0.4 | serde |
| reqwest | 0.11 | json |
| uuid | 1.0 | v4, serde |
| rand | 0.8 | - |
| regex | 1.0 | - |
| anyhow | 1.0 | - |
| thiserror | 1.0 | - |
| tracing | 0.1 | - |
| clap | 4.0 | derive |
| log | 0.4 | - |
| env_logger | 0.10 | - |
| sqlx | 0.7 | runtime-tokio-native-tls, postgres |
| futures | 0.3 | - |
| bytes | 1.0 | - |
| itertools | 0.12 | - |
Using Unknown Crates¶
Current behavior: If you try to import a crate not in the known-good list, you'll see an error:
Error: unknown Rust crate `my_crate`: no known-good version mapping exists.
To use this crate today, request that `my_crate` be added to the known-good list by opening an issue/PR.
Why so strict? Implicit wildcard (
*) dependencies can cause “works on my machine” failures when crate versions change. This policy will evolve—RFC 013 proposes inline version annotations,incan.tomlproject configuration, and lock files for full flexibility.
Current workarounds:
- Open an issue/PR to add the crate to the known-good list
- (Temporary) Manually edit the generated
Cargo.tomlto add your dependency
Adding to the Known-Good List¶
If you'd like a crate added to the known-good list:
- Open an issue or PR on the Incan repository
- Include the crate name, recommended version, and any required features
- Explain why this crate is commonly useful
The maintainers will test the crate and add it to src/backend/project.rs.
Coming Soon: Version Annotations and incan.toml¶
RFC 013 defines a comprehensive dependency system that will allow:
# Inline version annotations (planned)
import rust::my_crate @ "1.0"
import rust::tokio @ "1.35" with ["full"]
# incan.toml project configuration (planned)
[project]
name = "my_app"
version = "0.1.0"
[rust.dependencies]
my_crate = "1.0"
tokio = { version = "1.35", features = ["full"] }
This will enable any Rust crate while maintaining reproducibility.
Examples¶
Working with JSON (serde_json)¶
import rust::serde_json as json
from rust::serde_json import Value
def parse_json(data: str) -> Value:
return json.from_str(data).unwrap()
def main() -> None:
data = '{"name": "Alice", "age": 30}'
parsed = parse_json(data)
println(f"Name: {parsed['name']}")
Working with Time¶
from rust::time import Instant, Duration
def measure_operation() -> None:
start = Instant.now()
# Do some work
for i in range(1000000):
pass
elapsed = start.elapsed()
println(f"Operation took: {elapsed}")
HTTP Requests (reqwest)¶
import rust::reqwest
async def fetch_data(url: str) -> str:
response = await reqwest.get(url)
return await response.text()
async def main() -> None:
data = await fetch_data("https://api.example.com/data")
println(data)
Using Collections¶
from rust::std::collections import HashMap, HashSet
def count_words(text: str) -> HashMap[str, int]:
counts = HashMap.new()
for word in text.split():
count = counts.get(word).unwrap_or(0)
counts.insert(word, count + 1)
return counts
Random Numbers¶
from rust::rand import Rng, thread_rng
def random_int(min: int, max: int) -> int:
rng = thread_rng()
return rng.gen_range(min..max)
def main() -> None:
for _ in range(5):
println(f"Random: {random_int(1, 100)}")
UUIDs¶
from rust::uuid import Uuid
def generate_id() -> str:
return Uuid.new_v4().to_string()
def main() -> None:
id = generate_id()
println(f"Generated ID: {id}")
Type Mapping¶
Incan types map to Rust types:
| Incan | Rust |
|---|---|
int |
i64 |
float |
f64 |
str |
String |
bool |
bool |
List[T] |
Vec<T> |
Dict[K, V] |
HashMap<K, V> |
Set[T] |
HashSet<T> |
Option[T] |
Option<T> |
Result[T, E] |
Result<T, E> |
Understanding Rust types (optional)¶
Coming from Python?
If you’re new to Rust types like Vec, HashMap, String, Option, and Result, see
Understanding Rust types (coming from Python).
Limitations¶
-
Lifetime annotations: Rust's borrow checker and lifetime annotations are not exposed in Incan. Types that require explicit lifetime management may not work directly.
-
Generic bounds: Complex trait bounds on generic types are simplified. Some advanced generic patterns may need wrapper functions.
-
Unsafe code: Incan cannot call unsafe Rust functions directly. If you need unsafe operations, create a safe wrapper in Rust first.
-
Macros: Rust macros are not directly callable. Use the expanded form or a wrapper function.
-
Feature flags: Default features are used for common crates. For custom feature combinations, edit the generated
Cargo.tomlmanually.
Best Practices¶
-
Prefer Incan types: Use Incan's built-in types when possible. Use Rust types only when you need specific functionality.
-
Handle Results: Rust crate functions often return
Result. Use?or explicit matching:def safe_parse(s: str) -> Result[int, str]: return s.parse() # Returns Result def main() -> None: match safe_parse("42"): case Ok(n): println(f"Parsed: {n}") case Err(e): println(f"Error: {e}") -
Async compatibility: If using async Rust crates, make sure your Incan functions are also async.
-
Error types: Rust's error types can be complex. Consider using
anyhowfor simple error handling:from rust::anyhow import Result, Context def read_config(path: str) -> Result[Config]: content = fs.read_to_string(path).context("Failed to read config")? return parse_config(content)
See Also¶
- Error Handling - Working with
Resulttypes - Derives & Traits - Drop trait for custom cleanup
- File I/O - Reading, writing, and path handling
- Async Programming - Async/await with Tokio
- Imports & Modules - Module system, imports, and built-in functions
- Rust Interop - Using Rust crates directly from Incan
- Web Framework - Building web apps with Axum