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 inCargo.toml and Sifr interop policy in sifr.toml:
Cargo.toml
sifr.toml
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-diagnosticobserves parser, lowering, metadata, or diagnostic behavior and makes no Cargo build claim.contract-onlyverifies the named compiler or safety contract, but does not certify a package build or runtime behavior.cargo-probeexercises 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-observedexecutes the lifecycle or runtime behavior named by the row.
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 againstverification/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.
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 rootbridge 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.
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
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 anasync def. By default, returned futures must be Send; add @rust.async(thread_affinity=tokio_current_thread) only for explicitly current-runtime futures.
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.Callbacks
Plain top-levelCallable 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.
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.
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.