Skip to main content
Rust interop lets a Sifr package expose Rust-backed declarations while keeping the same compile-time contracts as ordinary Sifr code. Sifr resolves targets through Cargo metadata, validates bridge-compatible signatures before final build, and reports Rust interop failures as SIFR-RUST-* diagnostics. Rust interop is source-level Cargo integration. It is not Rust ABI loading, dlopen, or a C FFI layer. For library-first walkthroughs, start with blake3 or reqwest.

Package Setup

Declare Rust dependencies in Cargo.toml and Sifr interop policy in sifr.toml:
Cargo.toml
sifr.toml
Use sifr bridge check for Rust interop feedback. It uses the same package check path as sifr check, so decorator parsing, target resolution, bridge contracts, trust policy, and probe diagnostics match the compiler.
sifr repair --check reports drift in Sifr-managed Cargo projection files. sifr repair regenerates only Sifr-owned projection metadata and does not overwrite user-authored bridge files under src/bridges.

Documenting Rejected Syntax

Documentation must distinguish accepted Sifr from deliberately rejected historical syntax structurally. Open a rejected block with exactly ```sifr-rejected. For a stale spelling mentioned inline, place {/* rust-interop-rejected */} on that same physical line in MDX. Markdown files use <!-- rust-interop-rejected --> instead. Headings, surrounding prose, and words such as “no”, “stale”, or “rejected” do not mark an example as rejected. Accepted examples use sifr fences, and Sifr Rust decorators must never appear in python fences.

Compatibility Evidence

Published Rust-interop compatibility rows state how they were verified:
  • compiler-diagnostic observes parser, lowering, metadata, or diagnostic behavior and makes no Cargo build claim.
  • contract-only verifies the named compiler or safety contract, but does not certify a package build or runtime behavior.
  • cargo-probe exercises the real Cargo package graph. Positive directions build generated/package Rust code; negative directions may instead observe a required compiler rejection before Cargo execution.
  • runtime-observed executes the lifecycle or runtime behavior named by the row.
Verification tiers describe the breadth of the subject, not stronger evidence. Tier 1 and tier 3 require cargo-probe; tier 0 requires compiler-diagnostic; tier 2 and tier 4 explicitly name whether each row is contract-only, cargo-probed, or runtime-observed. A contract-only row never satisfies a runtime claim. The explicit structural bridge has runtime evidence for checked construction, allocation-free projection, typed call-scoped callbacks, nested generic or recursive records, and deliberate contract rejection. Manifest diagnostics accept unversioned Rust bridge configuration and reject the removed field. Sifr has no version-selection compatibility mode or fallback. A synthetic external package also has runtime evidence for a sealed opaque resource that constructs and projects a typed structural record before owned close. Its negative evidence rejects access through a bridge-local alias after close, keeps double-close state stable, redacts panic poisoning, and prevents Sifr code from constructing or reusing the resource directly.

Stable Support Claims

The following table is validated against verification/areas/rust_interop/data/stable_support_claims.json and is the only public stable-claim inventory. Its execution scope is part of every claim. In particular, zero_copy_bytes, zero_copy_view_matrix, arrow_record_batch, tensor_dlpack_bridge, and advanced_data_matrix are contract-only. advanced_data_runtime_matrix is the distinct generated-package runtime claim for the five listed data crates; it does not widen those contract-only rows. bridge_type_matrix is cargo-probed through generated package glue. Its certified bridge covers nested Serde/JSON values, display-mapped thiserror errors, byte buffers, and recursively nested dictionary/list values. Sifr dict iteration order remains unspecified across this bridge. panic_boundary_wrapper_emission executes synchronous generated package glue. Target panics are caught and redacted, panic=map_error(path) Rust adapters are signature-probed as fn(RustPanicErrorBridge) -> E with E: Display, mapper calls run behind a second protected boundary, and a mapper panic returns the original redacted RustPanicError. The compiler rejects mapper signatures that do not consume RustPanicErrorBridge and Result error channels that cannot represent both the mapped error and fallback. Panic recognition is nominal, including through aliases; similarly named errors and wrapper-only Result[T, RustPanicError] channels are rejected. Async panic=map_error(path) remains rejected until async panic-boundary emission is certified by its own runtime row. async_runtime_reqwest executes generated package glue against an ephemeral in-process HTTP server. The certified bridge accepts borrowed string inputs, reuses the generated current-thread Tokio runtime across calls, and observes request/server guard cleanup after a Sifr timeout cancels a delayed reqwest future. Packages with local async bridges are rejected with SIFR-RUST-ASYNC-0001 when ordinary Rust source under src/ or a declared bridge root constructs a Tokio runtime, calls a syntactically named block_on, imports a blocking executor call, or invokes tokio::task::block_in_place; same-file aliases and re-exports are resolved independent of declaration order. Cross-file re-exports reached only through an unresolved glob are governed by the declared package trust contract rather than this source audit. This does not certify arbitrary async services, macro-expanded operations, or async panic mapping. opaque_resource_matrix executes an owned generated opaque handle on the generated current-thread Tokio runtime. Its package-local bridge uses an ephemeral HTTP server with reqwest, a bundled rusqlite temporary database, and minimal deterministic Redis RESP and PostgreSQL wire-protocol loopbacks. The certified scope includes the exercised request/response frames, stable double close through an owned generated close=async_close member routed to the package bridge, operation rejection through a shared resource alias after close, runtime-guard poison redaction, bounded cleanup of every harness-owned tracked task, and observed temporary database removal. The bridge-local alias shares the resource identity; no Sifr-level clone policy is claimed. The certified alias rejection is the shared resource’s closed-state check. The Redis client metadata handshake is disabled, and Redis 1.6’s connection manager limits reconnects to two attempts, so the RESP harness certifies only the exercised connection and PING frames. Client-library-internal tasks are outside the harness counter. This does not claim general Redis or PostgreSQL server compliance. callback_subscription_ecosystem executes an owned, typed Send + Sync + 'static callback bridge against raw loopback WebSocket frames, a minimal Redis Pub/Sub RESP exchange, and a real notify watcher over a unique temporary directory. The bridge carries the exact declared bounded backpressure, overflow-as-error, and drain-shutdown policy. The package queue derives its capacity, overflow branch, and close-time drain/cancel behavior from that carried policy. The observed scope includes callback success and ordinary errors, stable panic redaction, foreign-thread notification entry, close-time queue drain, cancellation of a scheduled callback delivery before invocation, consuming async close, bounded joins, temporary-directory removal, and zero harness-owned active tasks or watchers. Named nested handlers retaining non-send or non-share-safe state, or callable values whose captures cannot be proven thread-safe, are rejected with SIFR-RUST-CB-0001 before Cargo probing. The same diagnostic rejects unresolved capture types and direct or sibling-transitive capture mutation because the retained adapter requires Fn, not FnMut. Mutation detection includes ordinary nonlocal rebinding, attribute and collection-item assignment or deletion, and collection-mutating method calls, including uses inside further nested helper functions, comprehensions, lambdas, interpolated strings, slice bounds, and starred expressions. Receiver types distinguish ordinary collection mutation from interior synchronization such as RwLock.write(). A walrus expression cannot rebind a declared nonlocal; use a statement assignment instead. Verified non-Copy values are cloned into the retained closure’s isolated construction scope, so the surrounding binding remains usable after attachment and on later loop iterations. That clone snapshots the value when the nested handler is declared; rebinding the surrounding name before attachment does not change the retained value. Attachment consumes the handler binding, so attaching or calling that same nested handler again is an ownership error. This certifies only the exercised local protocol frames and package bridge, not general WebSocket, Redis, or filesystem event semantics. zero_copy_runtime_matrix executes generated package glue for bytes, memmap2, bytemuck, and zerocopy. The owned buffer received by the bridge is moved into bytes::Bytes without changing its allocation, and a sliced alias remains valid after the original Rust owner binding is dropped. The same package mutates an anonymous mapping only while it is exclusive, seals it read-only without changing its address, and observes pointer-identical bytemuck and zerocopy views. For opaque crate-backed returns, the compiler binds view= to the exact returned Rust handle type and directly probes its declared Send and Sync obligations. Contract-only generated-record views retain their metadata-specific validation. The compiler rejects mutable views from shared owners, rejects returned call-lifetime views, and rejects owner-lifetime views across async suspension. Consuming close drops the view and the scenario requires one release with zero active views. This claim is limited to the generated bridge and those exact crate-backed transitions. advanced_data_runtime_matrix executes a locked generated package through shared sifr_arrow_bridge and sifr_tensor_bridge crates. An owned Sifr floating-point vector is moved into an Arrow array without changing its allocation, retained in a record batch registered with DataFusion, and checked against a Polars dataframe derived from those Arrow values by an explicit copy with the same field, dtype, and row count; no Arrow-to-Polars zero-copy claim is made. Separate owned vectors are moved without allocation change into ndarray and CPU-only Candle, where rank, shape, contiguous layout, strides, dtype, and device are observed. The ndarray owner is then consumed into a one-shot DLPack-style managed capsule without copying and is observed active before consuming close and released exactly once afterward. This safe bridge models DLPack ownership and metadata without exposing the unsafe C ABI. The package declares the architecture-specific native-link envelope covering the locked default-feature graph on arm64 and x86_64. Compiler diagnostics reject schema-root, shape/rank, and non-CPU device mismatches before Cargo. native_build_script is a locked cargo-probe claim for exact-pinned cc, bindgen, cxx, and zstd. Four direct wrapper crates expose their build-script and native identities through Cargo metadata before execution, write versioned artifacts only beneath OUT_DIR, and are built twice under --locked --offline --frozen to prove byte-identical evidence. Generated Sifr package glue observes all four artifacts and proves a zstd encode/decode roundtrip. The manifest declares the portable cxxbridge1, link-cplusplus, c++/stdc++, wrapper, and zstd native-link envelope; post-build evidence outside it is rejected. Removing either the zstd wrapper’s build-script permission or its native-link permission produces SIFR-RUST-TRUST-0001 before an armed build script can create its sentinel. This claim is certified for the checked-in Apple/GNU arm64 and x86_64 host envelope and requires a working C/C++ compiler plus a libclang installation discoverable by bindgen; it does not advertise an MSVC build-script envelope. proc_macro_trust is a locked cargo-probe claim for exact-pinned serde_derive 1.0.229 and prost-build 0.14.4. A trusted direct derive wrapper executes its own SifrGenerated macro in generated package code and separately proves its exact upstream serde_derive dependency compiled. A trusted build-script wrapper runs prost-build over an in-memory descriptor set, compiles the generated message, and exposes a versioned schema marker. Two fresh --locked --offline --frozen builds must produce byte-identical generated Rust. Package-wide trust validation covers direct build-time dependencies even when the Sifr declaration targets a local bridge. Removing either serde_derive proc-macro trust or the normalized prost_build Cargo-alias build-script trust produces SIFR-RUST-TRUST-0001 before either armed sentinel can execute. No protoc installation is required. The cargo_locked_offline evidence executes package sifr check, sifr build, and sifr run as a cargo-probe. The scenario uses exact-pinned indexmap 2.14.2 with default features disabled under --locked, --offline, and --frozen, preserves the authoritative package lock, and observes a cold probe cache miss followed by a warm cache hit. Generated lock preparation admits only exact package identities and checksums found in authoritative locks or trusted vendor sources; constrained probes cannot reuse a normal-mode cache entry. Missing locks and selected version, source, checksum, or feature drift are rejected without network access as SIFR-RUST-CARGO-0001. The ecosystem_cli_certification evidence builds and runs an exact-pinned generated package with clap 4.6.6, tracing 0.1.44, tracing-subscriber 0.3.23 using env-filter, and anyhow 1.0.104. The package-local bridge parses a real command and observes a filtered tracing event. anyhow::Error remains internal: the bridge maps its context chain into the declared CliError, while a direct function returning anyhow::Error is rejected as SIFR-RUST-TYPE-0001. This is supported-through-bridge for the exact crate graph and does not imply direct support for arbitrary CLI crate APIs or anyhow::Error values. The ecosystem_backend_certification evidence builds and runs an exact-pinned generated package with axum 0.8.9, tower-http 0.7.0, and sqlx 0.9.0. SQLx uses separate runtime-tokio and tls-rustls-ring-webpki features. It also uses postgres and macros, with default features disabled. The package binds an Axum server to 127.0.0.1:0, observes a tower-http response header through a raw loopback request, and shuts the task down cleanly. Its SQLx query macro compiles from a checked-in .sqlx/ artifact whose query and SHA-256 identity are validated. Under SQLx’s default cache search, package- and workspace-root .sqlx/ directories for every resolved bridge backend participate in probe and generated-build cache identity. Sifr—not fixture Cargo configuration—forces SQLX_OFFLINE=true and removes inherited DATABASE_URL from signature probes and generated builds. The negative test deliberately supplies a live DATABASE_URL through the bridge package’s .env; the valid control reaches Cargo without contacting that sentinel, so the compiler’s offline forcing is load-bearing. Missing or stale metadata is rejected before Cargo is spawned as SIFR-RUST-CARGO-0001. This is supported-through-bridge for the exact package and does not claim arbitrary Axum/tower-http APIs, live SQLx database connectivity, or product-level web framework support.

Direct Bindings

Use @rust(...) when a public Rust function has a bridge-compatible signature. The target is a dotted path, not a string. Package-authored Rust interop declarations use an ellipsis-only stub body: exactly .... Generated behavior comes from the validated Rust interop metadata.
Direct binding is for compatible Rust signatures. If a crate exposes lifetimes, generics, borrowed returns, trait objects, raw pointers, closures, unsafe fn, or another unsupported shape, write a bridge.

Local Bridges

Local bridges adapt Rust APIs that are not directly bridge-compatible. The Sifr target root bridge resolves to user-authored Rust modules under src/bridges. A local bridge is allowed to be a real adapter boundary: it can convert generated Sifr bridge types into backend Rust types, call one or more Rust functions, convert outputs back to Sifr bridge types, and map Result-typed Rust errors into the public Sifr error type. Use direct @rust(...) bindings only when the Rust signature already matches the Sifr declaration. Use a bridge function for anything that needs input shaping, output shaping, or error normalization.
Opaque instance members must return Result[...]. Self.method targets are limited to regular instance methods and use generated handle-state checks before entering the Rust panic boundary. They therefore require a message-shaped Error result, optionally combined with RustPanicError; PythonError and other richer shapes require a package-bridge adapter. Consuming close/aclose calls require an owned local binding (own at a function boundary); the compiler carries that rule through imports, aliases, and re-exports and rejects borrowed or field-based cleanup before Rust compilation.
src/bridges/tokenizer.rs
Package-local bridges may import generated bridge types from crate::__sifr_bridge::<module>. Shared bridge crates must not import those package-specific generated modules; they expose stable Rust types or sifr_runtime::interop helper types instead. Rust functions are not Sifr values. Sifr source cannot pass Rust closures, impl Fn, or Box<dyn Fn> into generated glue.

Async HTTP

Async Rust interop must be declared on an async def. By default, returned futures must be Send; add @rust.async(thread_affinity=tokio_current_thread) only for explicitly current-runtime futures.
The union member reserves the declared error surface, but current async code generation does not catch panics while polling the returned Rust future. Only synchronous generated panic containment is certified. panic=map_error(path) is therefore rejected on async def until generated async panic wrappers receive runtime certification. Blocking or CPU-heavy Rust calls are never hidden inside async scheduler paths. Declare them explicitly with the existing Sifr blocking or CPU-heavy annotations and call them through the appropriate offload workflow.

Zero-Copy And Views

Zero-copy declarations must make owner, lifetime, mutability, and view metadata explicit. Sifr rejects silent copy fallback. The following Arrow and DLPack examples demonstrate accepted contract-only declaration metadata. They do not claim that the named ecosystem crates have passed runtime-observed exchange or lifecycle certification.
Returned borrowed views cannot outlive their owner, mutable views require exclusive ownership, and async functions cannot suspend while holding a borrowed Rust view.

Callbacks

Plain top-level Callable parameters are call-scoped. Generated glue lends a non-Send, non-Sync callback bridge to Rust only for the duration of the call. Rust can invoke it synchronously, but storing it, returning it, or moving it to another thread fails the Cargo probe. The enclosing declaration must be synchronous and return Result[T, E | RustPanicError] with one distinct ordinary error. Nested callback containers, returned callbacks, and mutable-borrow callback arguments are rejected. The selected Cargo profile must use panic = "unwind"; an abort profile cannot contain panics originating in Sifr callback code. Each Rust callback invocation supplies owned bridge argument values (for example, an owned String for Sifr str); the generated adapter then lends or converts those values according to the Sifr callback signature.
Callback panics unwind only to the generated outer Rust panic boundary and are returned as a redacted RustPanicError. A callback Result error crosses the call-scoped bridge as its display string and is mapped by the package bridge into the target’s declared error. Because the outer boundary deliberately suppresses panic payloads and hook output, an assert inside Sifr callback code also loses its assertion message and source location and is observed only as the redacted RustPanicError. Use the callback’s declared Result error for application failures that need actionable diagnostics. Backpressure, overflow, and shutdown behavior remain mandatory for the separate thread-safe registration contract. The current source spelling is a top-level Callable paired with @rust.callback(...). Retained callbacks also require an unwind-capable Cargo profile and target panic policy; panic=abort and abort profiles are rejected because they cannot contain callback panics. Directly declared nested retained handlers own isolated clones of verified non-Copy captures, preserving enclosing bindings after attachment and across loop iterations. The capture clone is taken when the nested handler is declared, and attachment consumes the handler binding even though the surrounding captured names remain available. Capture types come from the lowered lexical binding, including locals initialized from attribute reads and method calls and user classes whose names shadow builtin inference helpers. The compiler checks sibling nested-function capture chains transitively, rejects callable-valued captures whose own captures lack compiler-known provenance, and follows further nested helper scopes while respecting their complete parameter lists and locals. Expression capture and mutation discovery also traverses comprehensions, lambdas, interpolated strings and nested format specifications, slice bounds, and starred expressions. It rejects assignment-target, deletion, and type-confirmed collection-mutating-method capture use that would require FnMut, while allowing synchronization-wrapper operations that retain Fn. Callback attachment checks remain active through imported functions, aliases, re-exports, and imported methods; an unsafe or unverifiable capture is rejected with SIFR-RUST-CB-0001 before Cargo probing regardless of the declaration’s module, while reuse of a consumed handler is rejected by the SIFR-OWN-* ownership diagnostics.
Thread-safe runtime subscription behavior remains limited to the rows whose execution scope is listed as runtime-observed; contract-only rows certify declaration policy but not background delivery or shutdown.

Diagnostics

Rust interop diagnostics are grouped by contract family: Run sifr --explain SIFR-RUST-CB-0001 for a stable explanation of SIFR-RUST-CB-0001. Rust interop never falls back from zero-copy to copying, never creates a hidden Tokio runtime, and never lets a Rust panic unwind through Sifr user code in recoverable builds.