Skip to content

Release 0.3

Incan 0.3 is the current development release. It picks up after the 0.2 line, which made the language surface more explicit around stdlib imports, Rust interop, library manifests, module state, and call-site generics.

0.3 now includes a new control-flow surface, richer enum behavior, and tighter tooling contracts. RFC 016 adds loop: and break <value> so loops can produce values directly, RFC 050 lets enums declare methods and adopt traits, and RFC 053 tightens the formatter contract so output is less dependent on local heuristics and more predictable across CLI, editor, and library entry points.

This page will grow as more 0.3 work lands. If you are looking for the shipped 0.2 story, start with Release 0.2.

For the current control-flow guidance, start with Control Flow. For the current source-layout contract, start with the Incan Code Style Guide. Use Formatting with incan fmt for the tool behavior. RFC 016 and RFC 053 record the design snapshots behind those behaviors.

What 0.3 is about

The 0.2 line made Incan's module, stdlib, and Rust interop boundaries much clearer. 0.3 continues from that baseline with a stronger emphasis on predictable generated output, contributor ergonomics, and small but meaningful control-flow ergonomics that remove repetitive boilerplate without weakening the language's explicit pattern model.

The release is still early, but the initial direction is already visible:

  • expression-oriented control flow should stay explicit, so infinite loops that return values use loop: and break <value> rather than hidden accumulator patterns
  • formatter output should be governed by explicit contracts, not scattered newline decisions
  • common Option / Result destructuring should have a concise control-flow form when the non-match case is intentionally a no-op
  • enums that cross string or integer boundaries should keep enum type safety while exposing one canonical raw representation
  • enum-owned behavior should live on the enum itself, and enums should be able to adopt the same trait protocols as models and classes
  • operator overloading should present traits as nominal capability contracts while keeping dunder methods as the explicit implementation hooks
  • user-facing tooling behavior should match the docs closely enough that CI and editor integrations can rely on it
  • testing should feel like a first-class workflow, with inline unit tests, fixtures, parametrization, selection, scheduling, and machine-readable reports owned by Incan rather than delegated to ad hoc scripts
  • ownership and generated-runtime ergonomics should improve structurally, not through one-off .clone() or .as_ref() patches

Migrating from 0.2

There are no required source migrations for the current 0.3 work. loop: and break <value> are additive control-flow features; existing while True: code remains valid.

Projects that gate on incan fmt --check should expect one-time vertical-spacing diffs when adopting a formatter that implements RFC 053. Those diffs are intentional: top-level def / model / type-like declarations get exactly two blank lines around them, following body-bearing members inside type bodies get exactly one blank line, and other same-scope transitions stay in the zero-or-one bucket.

if let and while let are additive. Existing match code keeps working unchanged; the new forms are available when a single successful pattern matters and the non-match path should do nothing.

Major additions

RFC 016 loop expressions and break <value>

Incan now has an explicit infinite-loop construct:

  • loop: for intentional infinite loops in statement position
  • break <value> to complete a loop: expression with a result
  • ordinary break and continue continuing to work for for, while, and statement-form loop:

This makes "search until found", retry loops, and similar control-flow patterns expression-oriented without forcing a mutable accumulator outside the loop.

See also: Control Flow, Book chapter 4, RFC 016.

RFC 053 formatter vertical-spacing contract

incan fmt now follows RFC 053's three-bucket vertical-spacing model:

  • Exactly two blank lines around top-level def, class, model, trait, enum, type, newtype, and rusttype declarations
  • Exactly one blank line before a following body-bearing member inside a type body
  • At most one blank line everywhere else, including import runs, adjacent constants/statics, ordinary statement blocks, and transitions involving module docstrings when no top-level spaced declaration is involved

The formatter also normalizes docstring payload indentation while collapsing actual docstring blank-line runs to one blank line, keeps abstract trait methods tight until a following default/body-bearing method, treats stand-alone comments as leading or trailing bundles even when their target statement wraps, preserves a single authored blank line between statement groups after nested suites, keeps short single-statement match arms inline, normalizes blank lines after suite headers and match-arm arrows, strips trailing blank lines at EOF, and allows two consecutive blank lines only at root level.

Long call-like expressions and signatures now participate in formatter wrapping: overflowing constructor calls, ordinary calls, function signatures, and method signatures are rewritten across multiple lines and respect the existing trailing-comma setting.

The same spacing contract applies through the CLI and the library formatter API. FormatConfig still controls ordinary formatting options such as indentation and line length, but vertical-spacing buckets and comment placement are not configurable.

See also: Incan Code Style Guide, Formatting with incan fmt, RFC 053.

RFC 049 if let and while let control flow

Incan now supports if let PATTERN = VALUE: and while let PATTERN = VALUE: in statement position.

Use if let when exactly one successful pattern matters and the non-match path should do nothing. Use while let when a loop should keep iterating only while one pattern keeps matching. Both forms reuse the same pattern semantics as match, keep bindings scoped to the successful branch or loop body, and leave full match as the right tool when multiple arms or explicit non-match behavior matter.

In v1, if let remains intentionally single-arm only and rejects else / elif. When the non-match path is semantically important, keep using match.

RFC 029 union types and narrowing

Incan now accepts anonymous closed union annotations with both canonical Union[A, B, ...] and A | B syntax. Concrete member values can flow into union-typed returns and bindings, source unions can flow into wider target unions, and unions containing None canonicalize through Option[...].

Union values must be narrowed before using member-specific methods. The compiler now supports isinstance(value, T) narrowing for true branches, else branches, wider unions, chained elif branches, and Option[Union[...]] values; is None / is not None narrowing for Option[...]-canonicalized unions; and match type patterns such as int(n) and str(s), with exhaustiveness checking for ordinary unions.

RFC 032 value enums

Incan now supports value enums with str and int backing values:

enum Environment(str):
    Development = "development"
    Production = "production"

enum HttpStatus(int):
    Ok = 200
    NotFound = 404

Value enum variants remain enum values. They are not subtypes of the backing primitive and do not compare equal to raw primitive values. The generated value() helper returns the canonical raw value, while from_value(...) returns Option[Enum] for explicit handling of unknown external values. Generated display, string parsing, and serde hooks use the raw representation for value enums.

RFC 050 enum methods and trait adoption

Enums can now declare methods and associated functions inside the enum body, after their variants. Use this when behavior belongs to the closed set itself, such as Direction.opposite() or BuildState.describe(), instead of pushing enum-owned logic into detached helper functions.

Enums can also adopt traits with with TraitName, using the same trait adoption surface as models and classes. This makes enum-backed protocols reusable without special-case compiler support while keeping existing enum semantics additive and variant sets closed.

RFC 028 trait-based operator overloading

std.traits.ops now exposes the RFC 028 operator protocol vocabulary for custom types. The basic arithmetic traits are joined by floor division, power, shifts, bitwise operators, matrix multiplication, pipe operators, unary inversion, indexing hooks, and explicit in-place compound-assignment traits for the supported +=, -=, *=, /=, //=, %=, @=, &=, |=, ^=, <<=, and >>= syntax.

Operator traits are nominal capability contracts for generic code. Dunder methods such as __add__, __floordiv__, __rshift__, __matmul__, and __getitem__ are the implementation hooks that satisfy those contracts. Compound assignment first checks for the explicit in-place hook such as __iadd__; if none exists, it falls back to ordinary binary operator assignment.

RFC 068 protocol hooks for core syntax

Core syntax now resolves through static protocol hooks for user-defined types. Custom types can participate in truthiness, len(...), membership, iteration, indexing, indexed assignment, and callable-object invocation by defining compatible hooks such as __bool__, __len__, __contains__, __iter__, __next__, __getitem__, __setitem__, and __call__.

The hook surface remains statically checked. Dunder methods are implementation hooks, while traits such as Bool, Len, Contains, Iterable, Iterator, Index, IndexMut, and fixed-arity callable traits are the nominal capability vocabulary for explicit adoption, bounds, docs, and diagnostics. Option and Result remain intentionally non-truthy; use explicit pattern checks for optionality and fallibility.

Duckborrowing and ownership-aware codegen

The backend now routes more generated-Rust ownership decisions through a centralized "duckborrowing" planner. This strengthens the compiler's ability to choose moves, borrows, mutable borrows, owned string materialization, .into(), and necessary .clone() calls at typed use sites instead of relying on scattered emitter-local fixes.

Practically, this reduces the need for users and library authors to add ownership-shaping workarounds such as .clone(), .as_ref(), str(...), or .into() in ordinary Incan code just to satisfy generated Rust. The planner now covers more call arguments, collection and tuple literals, assignments, returns, match scrutinees, string lookup probes, tuple unpacking, and Rust interop boundaries.

RFC 057 targeted generated-Rust lint suppression

Incan now supports @rust.allow(...) for narrow suppression of specific rustc or Clippy lints on the generated Rust item for one declaration. This is Rust-emission metadata for unavoidable generated-Rust warnings, not arbitrary Rust attribute injection and not project-wide lint configuration.

The decorator is item-only and covers functions, methods, models, classes, enums, and newtypes. Module-level rust.allow(...) directives are not supported. The compiler also rejects obvious broad lint groups including warnings, unused, clippy::all, clippy::pedantic, clippy::nursery, clippy::restriction, and clippy::cargo.

Deterministic incan.lock files

incan.lock no longer records the wall-clock time when the file was generated. The lock file now contains only reproducibility-relevant inputs such as the Incan lock format version, compiler version, dependency fingerprint, Cargo feature selection, and embedded Cargo.lock payload. Re-running incan lock against unchanged inputs should leave the file byte-for-byte unchanged, reducing noisy VCS churn in projects that commit lock files.

Older lock files that still contain the previous generated = "..." metadata continue to load, but newly written lock files omit it.

Default incan build and incan test also avoid rewriting an existing stale incan.lock during routine verification. When the fingerprint differs outside --locked / --frozen, the command warns and reuses the embedded Cargo.lock payload; run incan lock when you intentionally want to refresh the committed lock file.

RFC 018 testing language primitives

The language assert statement is now an always-on language primitive. Use assert expr[, msg] directly for ordinary checks; import std.testing when you need helper functions such as assert_eq, assert_is_some, fail, fixtures, parametrization, or marker decorators.

Testing decorators remain std.testing APIs rather than magic global names. @skip, @xfail, @slow, @fixture, and @parametrize must resolve through std.testing, and runner/discovery behavior remains part of RFC 019 rather than RFC 018.

assert call() raises ErrorType[, msg] and compiler-recognized std.testing.assert_raises[E](block, msg?) calls now share runtime panic-payload matching. Error payloads match either the exact kind name, such as ValueError, or the canonical Kind: message prefix.

RFC 019 first-class test runner

incan test now has a full runner contract instead of a thin compile-and-run path. Tests can live in conventional tests/test_*.incn / tests/*_test.incn files or inline module tests: blocks inside production source files. Inline tests can exercise same-file private helpers, and production incan build / incan run output still strips test-only declarations and imports.

Discovery now supports both def test_*() and explicit @test, and every collected case has a stable id. Those ids are used consistently by --list, -k, parametrized test names, JSON Lines output, JUnit XML, and duration reporting. That makes CI logs, reruns, and editor integrations much less dependent on incidental generated-Rust names.

The runner also picks up the testing ergonomics people expect from a modern test framework:

  • @fixture dependency injection, including function, module, and session scopes
  • yield fixture teardown that can reference setup locals and fixture parameters
  • tests/**/conftest.incn inheritance for conventional test suites
  • built-in tmp_path, tmp_workdir, and env fixtures
  • @parametrize(...) with stable ids, cartesian products, and param_case(...) for per-case ids or marks
  • marker selection with -m, strict marker registries via TEST_MARKERS, and default marks via TEST_MARKS
  • @skip, @xfail, @slow, @mark, @timeout, @resource, and @serial
  • collection-time @skipif / @xfailif probes using platform() and feature("name")

Parallel execution is now runner-level and resource-aware. --jobs N runs generated worker batches concurrently while each batch still executes through single-threaded libtest. @resource("name") prevents overlapping batches that share a resource key, and @serial forces exclusive execution. Session fixtures are cached once per worker batch, so --jobs 1 can reuse a session fixture across compatible collected files, while higher job counts keep one session instance per worker.

Reporting is also CI-ready. --format json emits JSON Lines records with schema_version: "incan.test.v1", --junit <path> writes JUnit XML, --durations N reports slow tests, --shuffle --seed N gives reproducible randomized order, --run-xfail treats expected failures as ordinary tests, and --nocapture opts into printing child output for passing tests. Timeout-killed workers can still bypass teardown, so timeout teardown remains best-effort.

See also: Testing in Incan, Tooling: Testing, std.testing reference, RFC 018, RFC 019.

RFC 004 async fixtures

@fixture now works on async def fixture functions. Async fixtures use the same decorator as synchronous fixtures, use yield exactly once, await setup before dependents run, and await teardown after yield before the runner continues through reverse dependency teardown.

Mixed sync and async fixture graphs compose under function, module, and session scopes. Parametrized tests still expand before fixture resolution, so function-scoped async fixtures run per expanded case while module and session fixtures reuse values according to their existing scope boundaries.

Timeout behavior stays runner-level. incan test --timeout and @timeout(...) from std.testing apply to generated test batches; there is no per-fixture timeout configuration. The runner awaits async fixture teardown after ordinary failures and panics while the worker remains alive, but timeout-enforced worker termination can still bypass remaining cleanup.

See also: Testing in Incan, Tooling: Testing, std.testing reference, RFC 004.

Detailed inventory

The sections above are the release story. The list below is the detailed inventory of language, compiler, runtime, tooling, and docs changes that have landed for 0.3, grouped roughly by theme rather than by the order work happened to land.

Control flow

  • Language/Compiler: Incan now supports loop: as an explicit infinite-loop construct in both statement and expression position, with break <value> completing the surrounding loop: expression and plain break remaining valid for for, while, and statement-form loop: (#327, RFC 016).

Compiler and code generation

  • Compiler/Codegen: Duckborrowing ownership planning is now centralized around typed value-use sites, covering Incan call arguments, Rust interop arguments, struct fields, collection and tuple elements, assignments, returns, match scrutinees, mutable aggregate parameters, collection lookup probes, loop/comprehension traversal, and backend-inserted generic Clone bounds. This removes several classes of generated-Rust borrow/move failures and reduces the need for user-authored .clone(), .as_ref(), str(...), and .into() workarounds (#121).
  • Compiler/Codegen: Generic class type-owned factories can now construct and return Self from @classmethod and @staticmethod bodies. The compiler binds cls(...) inside classmethods, lowers Type[T].factory(...) as a Rust associated call instead of a value-position index expression, and the LSP surfaces cls hover/completion inside classmethod bodies (#388).
  • Compiler/Codegen: Default argument expressions that call helpers imported into the defining module now emit those helper calls with the required module qualification when the default is expanded at another call site. This fixes generated Rust failures such as omitted defaults expanding to an unqualified fallback() in test runners or downstream modules (#395).
  • Compiler/Codegen: Ordinary anonymous union wrappers are now shared through the generated crate root for multi-file source modules, so same-shaped unions can be forwarded across modules and member literals can call imported union-typed functions without producing distinct or unqualified Rust wrapper types (#457, #461).
  • Compiler/Codegen: Wide ordinary-union isinstance chains now fully lower before Rust emission, preserving the documented chained narrowing surface instead of leaving runtime isinstance(...) calls in generated Rust (#458).
  • Compiler/Codegen: Generated Rust now retains Rust enum imports that are referenced only from match patterns, including prost-style patterns such as Some(RelType.Read(_)) (#459).
  • Compiler/Codegen: std.testing.assert_eq and assert_ne now isolate their generated Rust operands before comparing them, so checks such as assert_eq(plan_encoded_len(plan) > 0, true) emit valid Rust instead of a chained-comparison parse error.
  • Compiler/Codegen: Cross-module trait-bound propagation no longer lets a same-named external generic helper rewrite a local non-generic function signature. This keeps std.testing.timeout(...) independent from std.async.time.timeout(...) even though both helpers share the same leaf name.
  • Compiler/Codegen: RFC 032 value enums now lower their raw-value metadata into IR and generate value(), from_value(...), display, string parsing, and serde implementations that use the canonical raw representation while keeping message() variant-name based (#317, RFC 032).
  • Compiler/Codegen: RFC 025 now preserves distinct same-generic-trait instantiations on model, class, and enum declarations, allows trait-backed same-name methods, resolves same-family calls by argument types or explicit expected return type, enforces T with Trait[F] generic bound arguments, and emits separate Rust trait impls (#150, RFC 025).
  • Compiler/Codegen: @rust.allow(...) now emits targeted Rust #[allow(...)] metadata for specific generated Rust items when an Incan declaration intentionally accepts a narrow rustc or Clippy lint. The decorator supports functions, methods, models, classes, enums, and newtypes, rejects module-level directives, and blocks broad lint groups such as warnings, unused, and the common Clippy group lints (#337, RFC 057).
  • Compiler/Codegen: Normal generated Rust no longer emits compiler-generated dead_code or unused_imports allowances. The backend now prunes unused private declarations and imports, keeps Rust extension-trait imports when method lookup needs them, keeps public reexports warning-clean without suppression, and uses narrow #[expect(dead_code)] markers only where retained private fields are required for Incan semantics but Rust cannot observe a read (#214).
  • Compiler/Runtime: Generated Rust now routes the in-scope panic-backed collection and JSON extraction paths, plus proc-macro decorator misuse stubs, through named stdlib helpers instead of open-coded fallback or panic! shims. The narrow checked-newtype construction panic remains tracked separately (#351).
  • Compiler/Typechecker: Typechecker architecture is now split across clearer internal ownership boundaries. Lowering-facing semantic snapshots live outside the main checker state, stdlib trait-method fallback lookup comes from the canonical stdlib registry surface, and import materialization is decomposed into explicit module, stdlib, pub, and Rust import paths without changing language behavior (#283).
  • Compiler/Typechecker: Unsupported trait-typed local annotations now produce an Incan diagnostic instead of reaching Rust codegen as invalid bare trait local types (#462).
  • Language/Compiler: Enums can now declare methods and associated functions after their variants and adopt traits with with, bringing enum-owned behavior and trait protocol participation into parity with models and classes (#334, RFC 050).
  • Language/Compiler: Core syntax now uses statically checked protocol hooks for user-defined truthiness, length, membership, iteration, indexing, indexed assignment, and callable-object invocation (#86, RFC 068).

Tooling and formatter

  • Tooling: RFC 020 completes the Cargo policy contract for generated builds and tests. incan build, incan run, and incan test now accept --offline, --locked, --frozen, explicit --no-* environment overrides, Cargo args forwarding, and matching CI environment defaults for restricted-network and reproducible workflows (#38, RFC 020).
  • Tooling: incan.lock files no longer include a volatile generation timestamp. New lock files are deterministic for unchanged dependency inputs, while older lock files with generated = "..." metadata remain readable.
  • Tooling: Default incan build and incan test now warn and reuse an existing stale incan.lock payload instead of rewriting the project lockfile as a side effect of routine verification. incan lock remains the explicit refresh command, while --locked and --frozen keep rejecting stale lockfiles (#446).
  • Tooling: incan tools doctor now includes advisory offline-readiness diagnostics in text and JSON output, reporting Cargo availability, effective Cargo home, cache/config hints, and remediation steps before users rely on RFC 020 offline or frozen policy in restricted environments (#460).
  • Tooling/Editor: incan tools doctor and the VS Code/Cursor Incan: Doctor command now report local incan / incan-lsp path resolution, cargo-bin symlink state, and recovery guidance for stale editor diagnostics or mismatched local binaries (#426).
  • Compiler/Tooling: RFC 048 checked contract metadata is now compiler-visible through canonical model bundle validation, project materialization, deterministic incan tools metadata model emit from projects, bundle JSON, and .incnlib artifacts, artifact embedding for publishable bundles, strict checked API docstring validation, incan tools metadata api JSON extraction, and LSP hover/emit integration (#205, #438, RFC 048).
  • Compiler/Tooling: incan tools metadata api emits checked public API metadata JSON for an Incan source file or project directory, including public declarations, checked signatures, stable anchors, parsed docstring sections, public import aliases with resolved targets, resolved decorator paths, safe decorator arguments, safe public const values, and model field alias/description metadata (#205, #438).
  • Tooling/Editor: LSP hover now previews RFC 048 checked API metadata for public declarations and selected public model/class members after successful typechecking, and workspace/executeCommand command incan.metadata.model.emit emits contract-backed model source or bundle JSON from project, bundle, or artifact metadata (#205).
  • Tooling/Editor: LSP hover and completion details now surface RFC 032 value-enum metadata. Public value-enum hovers use Incan backing spellings (str / int), public enum variant hovers show raw values, and local enum/variant completions include backing type and raw-value details (#166, RFC 032).
  • Compiler/Tooling: CLI compilation, LSP dependency collection, and the test runner now share the frontend's canonical source-module resolver for local module paths, logical module identity, stdlib source classification, and source-root fallback behavior (#285).
  • Compiler/Tooling: RFC 053’s vertical-spacing contract is now reflected in incan fmt: top-level def / model / type-like declarations keep two blank lines around them, adjacent constants/statics stay grouped unless they border one of those declarations, trait abstract methods stay tight until a following body-bearing member, docstring indentation is normalized while actual blank-line runs collapse to one blank line, single readability gaps between statement groups survive nested suites, short single-statement match arms stay inline, blank lines after suite headers and match-arm arrows are normalized, trailing EOF blank lines are removed, two consecutive blank lines are allowed only at root level, and stand-alone comments attach as leading/trailing bundles even when the formatter wraps the target statement (#336, RFC 053).
  • Compiler/Tooling: incan fmt now wraps overflowing call and constructor argument lists, plus function and method signatures, across multiple lines with trailing commas controlled by the existing formatter setting (#336, #248).
  • Tooling: Vocab extraction helper tests now reuse the workspace lockfile when resolving helper dependencies, so focused vocab extraction coverage can run in restricted-network environments once local workspace dependencies are present (#211).
  • Tooling: Vocab WASM desugarers now get enough fuel to parse, walk, and serialize nested public AST output from real wasm32-wasip1 companion crates. Regression coverage runs a deeply nested vocab block through incan run with a let statement whose value contains nested helper-call output, list arguments, action requirements, page interactions, and required-input constraints to guard the desugar boundary reported in #455.
  • Tooling/CI: Stable Ubuntu, macOS, and MSRV test gates now use sccache-backed nextest slice partitions while preserving the aggregate CI check names, and the release smoke gate uses a dedicated release-profile target cache to reduce duplicated compiler work without dropping broad coverage (#451).
  • Tooling/Test runner: RFC 019 expands incan test with explicit @test discovery, stable test ids for -k and --list, JSON Lines reports with schema_version: "incan.test.v1", JUnit XML output, duration reporting, deterministic shuffle/seed support, --run-xfail, conftest inheritance for conventional tests, inline module tests: execution, parametrization, fixtures, conditional markers, timeouts, output capture controls, and worker scheduling with --jobs, @resource, and @serial (#77, RFC 019).
  • Tooling/Test runner: RFC 019 fixture lifecycles now run through worker-batch harnesses, including compatible cross-file session fixture reuse with --jobs 1, per-worker session reuse with --jobs N, module/session teardown timing, and captured yield fixture teardown locals (#77, RFC 019).
  • Tooling/Test runner: RFC 004 async fixtures now use the existing @fixture decorator on async def, await setup before dependents run, await post-yield teardown, compose with synchronous fixtures under function/module/session scopes, and resolve after parametrized test expansion while keeping timeout policy at the test-batch level (#78, RFC 004).
  • Tooling/Test runner: Worker batches now fall back to per-file harnesses when multiple source files define colliding top-level Rust item names. Compatible files still batch together for session fixture reuse, while projects with repeated helper/model names avoid generated Rust duplicate-definition failures.
  • Tooling/Test runner: incan test now preheats stale generated Cargo harnesses with cargo test --no-run, fingerprints successful preheat state next to each generated harness, and uses a one-writer lock so concurrent CLI/LSP-style runs do not stampede Cargo (#272).
  • Tooling/Test runner: incan lock and implicit first-use lock generation now preheat non-trivial dependency graphs with cargo test --no-run into the same debug target domain used by generated test harnesses, then stamp the dependency preheat fingerprint so unchanged relocks stay cheap (#272).

Parser and syntax

  • Language/Compiler: RFC 029 adds anonymous closed union annotations with canonical Union[A, B, ...] and A | B syntax. The compiler normalizes duplicates, nested unions, ordering, and None-containing unions, accepts member-to-union and union-to-union assignability, lowers ordinary unions to generated closed Rust enums, preserves None unions on the existing Option[...] path, and supports isinstance narrowing for true branches, else branches, wider unions, chained elif branches, and Option[Union[...]], plus is None / is not None narrowing and exhaustive match type patterns (#163, RFC 029).
  • Language/Stdlib: RFC 028 expands std.traits.ops into the nominal operator capability vocabulary for custom types, including FloorDiv, Pow, shifts, bitwise operators, pipe operators, MatMul, unary Not, GetItem / SetItem, and explicit in-place compound-assignment traits for +=, -=, *=, /=, //=, %=, @=, &=, |=, ^=, <<=, and >>= (#162, RFC 028).
  • Language/Stdlib: RFC 055 introduces std.fs as the path-centric filesystem module: Path, File, OpenOptions, directory entries, metadata, disk usage, structured IoError, whole-file byte/text helpers, chunked file handles, traversal, globbing, copy/move, recursive deletion, links, permissions, and explicit durability syncs (#286, RFC 055).
  • Language/Compiler: Incan functions and methods can now declare variadic positional and keyword captures with *args: T and **kwargs: T, which bind as List[T] and Dict[str, T] inside the callable. Static call-site unpacking with f(*xs) and f(**kw) supports rest-aware callees and fixed-parameter callees when the compiler can prove the unpacked shape. Runtime list and dictionary literals now support spread entries with [*xs] and {**kw}, while invalid destinations such as [**xs] and {*xs} are rejected with targeted diagnostics (#83, RFC 038).
  • Library authoring: incan_vocab is now versioned as 0.2.0, marking the first contract bump after the initial 0.1 companion-crate API. The crate README now tracks version history and separates crate semver from the serialized VOCAB_METADATA_VERSION and WASM_DESUGAR_ABI_VERSION compatibility constants.
  • Language/Compiler: RFC 040 adds scoped DSL surface descriptors to incan_vocab 0.2.0 and library manifests. Imported vocab crates can now publish descriptor metadata for operator-like glyphs, binding-like glyphs, and expression-form surfaces; the parser recognizes descriptor-enabled leading-dot paths and scoped operator glyphs inside owning vocab blocks while preserving ordinary syntax outside those blocks (#174, RFC 040).
  • Language/Compiler: Incan now supports if let PATTERN = VALUE: and while let PATTERN = VALUE: for single-pattern control flow. Parsing, formatter round-trips, typechecking, scoping, lowering, and Rust emission now follow the same pattern semantics as match, while if let stays single-arm only and rejects else / elif branches in v1 (RFC 049, #333).
  • Language/Compiler: Incan now supports RFC 032 value enum declarations with str and int backing values. The parser and formatter preserve raw variant assignments, while declaration validation rejects missing values, duplicate raw values, mismatched literal types, payload-bearing variants, generated-helper name collisions, and generic value enums (#317, RFC 032).
  • Language/Compiler: RFC 083 adds declaration-level symbol aliases and same-type method aliases. Top-level forms such as mean = avg and pub average = alias avg resolve to existing callable or type-like symbols, method aliases such as mean = avg project the target method signature without creating wrapper methods, checked API metadata records alias identity, and library manifests now export aliases as alias metadata instead of duplicated declarations (#437, RFC 083).
  • Language/Compiler: Public classes now preserve authored field visibility. Non-pub class fields stay private after formatter round-trips and member access outside the owning class is rejected, while methods on the class can continue to use private backing fields (#246).
  • Compiler/Parser: Multiline function and method parameter lists now accept a trailing comma before ), including receiver-only method signatures such as def get(self,) -> int when written across lines (#394).
  • Tooling: incan fmt now wraps long parenthesized logical expression chains at and / or breakpoints when the inline form exceeds the configured line-length target (#484).
  • Language/Testing: RFC 018's assert expr[, msg] language primitive is always available without importing std.testing. The std.testing helpers mirror assertion failure behavior for call-style checks, raises checks, and unwrap-style Option / Result helpers, while marker decorators remain imported std.testing APIs.
  • Language/Testing: Inline module tests: blocks in production source files are now discovered and executed by incan test, while production build/run output still strips those test-only declarations and imports (RFC 018, #76).
  • Runtime/Async: std.async now documents cancellation-safety contracts and exposes channel reservation APIs so critical sends can reserve capacity before committing messages (#415, #416).
  • Runtime/Async: std.async.time adds timeout_join, timeout_join_ms, and a must-use TimeoutJoinOutcome so spawned work can keep running after a deadline while callers retain the live JoinHandle for later observation or explicit abort (#417).
  • Runtime/Async: std.async.sync.Barrier.wait() now uses Incan-owned generation bookkeeping so cancelling a pending wait withdraws that participant and frees its arrival slot instead of corrupting barrier progress (#418).
  • Language/Compiler: List and dict comprehensions now accept tuple-unpack iteration targets such as for idx, name in enumerate(xs), matching ordinary for loop binding syntax (#483).
  • Language/Compiler: Multi-file web builds now retain private route-decorated handlers and the private models they use in dependency modules, so route registration works without forcing those declarations public (#117).

Versioning and release track

  • Project lifecycle tooling: Added lifecycle commands for interactive incan new / incan init, incan version, and incan env, plus project lifecycle documentation and incan.toml environment metadata support (#73).
  • Dependency policy: The rust-analyzer proc-macro API dependency is patched locally to request postcard without default features, removing the unmaintained atomic-polyfill crate from the workspace dependency graph and letting cargo deny check run without the RUSTSEC-2023-0089 advisory ignore (#260).
  • Dependency policy: The workspace now builds against Wasmtime 44.0.1 / Wasmtime WASI 44.0.1 and raises the Rust MSRV to 1.92, matching Wasmtime 44's compiler requirement.
  • Dependency policy: Dependabot security alerts for the VS Code extension lockfile, docs-site Python pins, and Rust rand lock entries are remediated, while repo-owned GitHub Actions are moved to Node 24-compatible action releases (#475, #464).
  • Project metadata: Workspace package metadata, Cargo lock entries, stdlib version-check docs, and checked-in pro example lockfiles now identify the development line as 0.3.0-dev.33.

Known limitations (0.3)

  • incan fmt remains intentionally conservative on broader wrapping and may still leave indivisible tokens or unsupported expression shapes beyond the documented 120-character line-length target. RFC 053 / #336 narrows the vertical-spacing contract and adds call/constructor wrapping, while #248 adds common function/method signature wrapping; this is still not a general wrapping/configuration overhaul.
  • 0.3 is still a development release. This page is intentionally incomplete until more post-0.2 work lands.

RFCs implemented

  • Async fixtures: RFC 004
  • Hatch-like tooling and project lifecycle CLI: RFC 015
  • Loop expressions and break values: RFC 016
  • Testing language primitives: RFC 018
  • Trait-based operator overloading: RFC 028
  • Union types and type narrowing: RFC 029
  • Value enums: RFC 032
  • Variadic positional arguments and keyword capture: RFC 038
  • Checked contract metadata and interrogation tooling: RFC 048
  • if let and while let pattern control flow: RFC 049
  • Enum methods and enum trait adoption: RFC 050
  • Formatter vertical spacing buckets: RFC 053
  • Path-centric filesystem APIs: RFC 055
  • Targeted generated-Rust lint suppression: RFC 057
  • Protocol hooks for core syntax: RFC 068