Release 0.3¶
Incan 0.3 builds on the 0.2 line by making larger programs feel less improvised: richer source-level language features, a much broader standard library, a stronger test runner, better Rust interop, and fewer generated-Rust ownership surprises.
If 0.2 was mostly about explicit namespaces, library manifests, and Rust boundary cleanup, 0.3 is about using that structure for real application code. The release adds typed numerics, expression-oriented control flow, enum behavior, protocol hooks, Rust trait adoption, graph/collection/JSON/regex/datetime/logging/encoding/hash/compression stdlib modules, lazy iterator pipelines, Result combinators, first-class testing workflows, and tighter lockfile/formatter/tooling contracts.
If you are looking for the previous release story, start with Release 0.2. For current user docs, start with Control Flow, Choosing numeric types, Testing in Incan, the standard library reference, and Rust interop.
What 0.3 is about¶
The main direction is not "more syntax for its own sake." 0.3 moves common project patterns into documented language and stdlib surfaces so users can write less Rust-shaped scaffolding and contributors can keep compiler behavior tied to explicit metadata.
- Language: Numeric widths, fixed-scale decimal annotations,
loop:expressions,if let/while let, union narrowing, value enums, enum methods, computed properties, decorators, aliases, partial callables, protocol hooks, variadics, generators, and pattern alternation make the Python-shaped surface more expressive while staying statically checked. - Stdlib: Collections,
OrdinalMap, graphs, JSON values, regex, datetime, logging, encoding, hashing, compression, filesystem, I/O, temporary files, UUIDs, iterator adapters, andResulthelpers move ordinary application needs out of ad hoc Rust interop. - Interop: Rust crate imports,
rusttype, trait adoption, associated types, derived Rust metadata, metadata-backed call boundaries, and generated Rust retention now cooperate better with real Rust crates and protobuf-style APIs. - Tooling:
incan test,incan fmt,incan lock, lifecycle commands, doctor diagnostics, checked API metadata, LSP metadata, and generated Rust audits are more deterministic and CI-friendly. - Architecture: More behavior is registry- and metadata-driven, and generated Rust relies less on scattered special cases.
Migrating from 0.2¶
Most ordinary 0.2 programs should continue to compile. The changes below are the ones most likely to show up during adoption.
- Formatter output may change.
incan fmtnow follows RFC 053's vertical-spacing buckets and wraps more calls/signatures. Projects that runincan fmt --checkshould expect one intentional formatting diff. - Numeric names are more reserved. Existing
intandfloatcode keeps working, but project-local bare type names such asdecimal,numeric,bigint,integer,smallint,real, ordoublecan now collide with canonical numeric vocabulary. Rename local aliases or use the new exact forms such asdecimal[12, 2]. - Testing imports are clearer. The language
assertstatement is always available, but testing decorators and helpers remainstd.testingAPIs. Files that use@fixture,@parametrize,@skip,assert_eq, or similar helpers should import them explicitly. - Lockfiles are less noisy.
incan.lockno longer records generation timestamps, and routinebuild/testruns warn and reuse stale lock payloads instead of rewriting committed lockfiles. Runincan lockwhen you intentionally refresh the lock. - New features are additive.
loop:,if let,while let, value enums, protocol hooks, iterator adapters, andResultcombinators do not require rewriting existingmatch,while True, helper-function, or nested-matchcode.
Feature guide¶
Use this section as the map. The release note names each larger feature, says what it is for, and links to the docs that carry the real detail.
Language features¶
- Numeric types and fixed-scale decimals: Use exact widths and schema-shaped names when a boundary needs them, while keeping
intandfloatergonomic for ordinary code. Start with Choosing numeric types, then Numeric semantics (RFC 009, #325). - Validated newtypes and checked coercion: Move primitive invariants into named source types with checked construction, optional implicit coercion, and explicit opt-out when an API should require construction at the boundary. Read Newtypes and Book: newtypes (RFC 017).
- Loop expressions: Use
loop:plusbreak <value>for search, retry, and accumulator-free loops that produce a value. Read Control Flow (RFC 016, #327, #387). - Pattern control flow: Use
if letandwhile letwhen one successful pattern should run and the miss case should do nothing. Read Control Flow (RFC 049, #333). - Union narrowing and pattern alternation: Model inputs that can take several shapes, then narrow them with checked patterns instead of hand-written tag logic. Read Union types (RFC 071, RFC 029).
- Value enums: Keep enum type safety while exposing canonical
strorintrepresentations for external values. Read Enums and Modeling with enums (RFC 032, #317). - Enum methods and trait adoption: Put enum-owned behavior on the enum and let enums adopt the same trait protocols as other source types. Read Enums and Traits as language hooks (RFC 050, #334).
- Computed properties and protocol hooks: Define property-like readers and dunder-backed operator/protocol behavior without pushing users into Rust-shaped wrappers. Read Traits as language hooks and Derives and traits (RFC 046, RFC 068, RFC 028, #86, #162, #203).
- Model, class, primitive type-argument reflection, and type tokens: Inspect source field metadata and class names from concrete values, generic value helpers, explicit model type arguments, or primitive type arguments with
__fields__()and__class_name__(). Type-token overloads such ascast(expr, int)let libraries keep type-directed return types precise without string target names, and aliases preserve that overload surface for compatibility spellings. Read Reflection andstd.reflection(#712, #714, #715, #750). - Decorators: Typecheck user-defined decorators for functions, async functions, and methods so later references see the decorated callable shape, concrete decorated callable values expose
__name__for registry-style decorators, decorated generic wrappers keep explicit type-argument calls, and decorated wrappers preserve source default-argument calls when the callable surface is unchanged. Read Decorators, Functions, and Checked API metadata (RFC 036, #170, #640, #694, #703, #715). - Symbol aliases: Export an existing callable or type-like symbol under another name without pretending it is a hand-written wrapper. Read Symbol aliases (RFC 083, #437).
- Callable presets with RHS
partialdeclarations: Writepub get = partial route(method="GET")when a new API name is really the same callable with named defaults, not a new function body. Read Callable presets explained, then Callable presets (RFC 084, #453). - Variadics and call unpacking: Describe call shapes that accept or forward flexible argument lists without losing static checks. Read Functions (RFC 038, #83).
- Generators and lazy iterators: Build pipelines with generator values, lazy adapters, and explicit terminal consumers such as
collect,count,find, andfold. Read Generators and Generator semantics (RFC 006, RFC 088, #127, #324, #386). - Scoped DSL surfaces: Let vocab crates activate scoped block, clause, glyph, leading-dot, symbol, and expression-list item syntax for their own DSL contexts instead of turning library-specific syntax into global parser behavior. Read Author library DSLs with incan_vocab (RFC 040, RFC 045).
Rust interop and API metadata¶
- Rust trait adoption from Incan source: Newtypes and rusttypes can adopt Rust traits with
with Trait, method-levelfor Trait, and associated type declarations. Read Rust interop, Rust types for Python developers, andstd.traits(RFC 043, #200). - Derived and inspected Rust metadata: Supported
@rust.derive(...), associated types, inspected Rust signatures, and metadata-backed call boundaries now survive further through generated calls. Read Derives and traits and Rust-shaped confidence (RFC 041, #175). - Targeted generated-Rust lint suppression: Use
@rust.allow(...)when source semantics intentionally require a narrow generated-Rust lint allowance, without turning off warnings for whole projects or generated modules. Read Rust interop (RFC 057). - Rust imported calls follow Incan argument binding: Imported Rust free functions can use keyword arguments when inspected or shipped Rust metadata provides parameter names; codegen lowers those calls to the positional Rust call shape (#718).
- Checked public API metadata: Public declarations, aliases, partials, models, enum variants, decorator-backed callable context, and facade alias projections can be emitted for tools and downstream consumers. Read Checked API metadata and LSP protocol support (RFC 048, #205, #438, #694, #695).
Standard Library¶
std.async: Awaitable races, channel reservation, timeout joins, cancellation-safe barriers, and un-awaited-call diagnostics move async workflows into documented stdlib APIs (RFC 039, #173, #415, #416, #417, #418, #146).std.collections: Ordered, sorted, counter, queue, stack, multimap, bidict, andlist.repeat(value, count)workflows have first-party containers and helpers; see also Choosing collection types (RFC 030, RFC 069, #385).std.collections.OrdinalMap: Deterministic immutable key-to-ordinal lookup supports schemas, catalogs, dictionary-encoded domains, and reproducible serialized lookup tables (RFC 101).std.compression: Gzip, zlib, deflate, bzip2, lzma, zstd, and Snappy-oriented byte/stream workflows are codec-explicit; see Compression (RFC 061).std.datetime: Dates, times, datetimes, durations, parsing, formatting, clocks, and timezone offsets share one temporal vocabulary; see Dates and times (RFC 058).std.encoding: Base64, hex, URL, and related byte/text transforms are strict and named; see Binary-text encoding (RFC 064).std.fs: Path-centric filesystem work covers paths, metadata, directory traversal, and file operations; see File I/O (RFC 055).std.graph: Directed graph, DAG, traversal, dependency ordering, path query, and cycle-aware workflows are available without ad hoc containers; see Working with graphs and Graph model (RFC 047).std.hash: Byte, file, and reader hashing use algorithm-specific helpers with normalized digest output; see Hashing data (RFC 065).std.io: In-memory byte streams and buffered readers cover byte-oriented I/O without direct Rust interop (RFC 056).std.json:JsonValuesupports dynamic payload construction, inspection, conversion, and extraction at API boundaries; see Dynamic JSON (RFC 051).std.logging: Structured logging gives modules stable logger names, levels, fields, and runtime-friendly generated Rust output; see Logging (RFC 072).std.telemetry: Pure telemetry data types carry structured attributes, resources, scopes, and trace context identifiers without configuring exporters or background providers (RFC 072).std.regex: Safe-default regular expressions cover matching, captures, iteration, splitting, and replacement without backtracking-only features; see Regular expressions (RFC 059).std.result:Result[T, E]gained Rust-shapedmap,map_err,and_then,or_else,inspect, andinspect_errhelpers for fallible pipelines (RFC 070, #386).std.tempfile: Scoped temporary files and directories are first-party test and application resources (RFC 010).std.testing: Fixtures, parametrization, markers, temp/env fixtures, async fixtures, and assertion helpers back theincan testworkflow; see Testing in Incan (RFC 018, RFC 019, RFC 004, #76).std.uuid: UUID parsing, formatting, generation, version inspection, and byte/string conversion are available as source-defined helpers; see Working with UUIDs (RFC 060).
Tooling¶
incan test: Inlinemodule tests:blocks are discovered by the runner, with fixtures, parametrization, markers, resource-aware parallelism, JSON Lines, JUnit XML, durations, shuffling, and--nocapture; read Tooling: Testing and Testing in Incan (RFC 018, RFC 019, RFC 004, #76).incan fmt: Formatting follows the vertical-spacing contract, wraps more long calls and signatures, supports leading-dot fluent method chains with attached comments, and preserves expression-position vocab blocks such asquery { ... }; read Formatting withincan fmtand the Code Style Guide (RFC 053, #73, #756).- Cargo policy and lockfiles:
incan build,incan run, andincan testpropagate offline, locked, and frozen policy whileincan lockowns intentional lock refreshes; read Project configuration (RFC 020, #460). - GitHub Actions installation: The reusable
install-incanaction installs the vocab companion targetwasm32-wasip1by default, so downstream projects can run dependency-vocabfmt,test, and build checks in clean CI checkouts without a separate target-install step (#188). - Lifecycle and diagnostics:
incan new,incan init,incan version,incan env, andincan tools doctorcover project startup, environment inspection, offline readiness, and editor binary health; read Project lifecycle (RFC 015, #426).
Bugfixes and Hardening¶
This section is grouped by outcome rather than by every minimized repro. Issue numbers are kept for traceability when you need the exact bug report.
Compiler Correctness¶
- Argument planning is shared: Ownership and Rust-boundary coercion now route through one argument-use plan instead of parallel caller/emitter heuristics, including temporary string expressions passed to Rust
&strparameters and Rust enum variants that ownStringpayloads (#716). - Duckborrowing covers more real use sites: Generated Rust handles arguments, returns, assignments, match scrutinees, aggregate elements, lookups, comprehensions, mutable aggregate parameters, Rust calls, and generated
Clonebounds with fewer user-authored workarounds (#121, #241, #364, #366, #367, #372, #383, #391, #602). - Release smoke paths are less fragile: InQL and release smoke testing fixed loop-item fields, union call arguments, storage-rooted match scrutinees, static list index assignment, typed
assert falselowering, and const model metadata constructors (#620, #621, #622, #627, #630, #644, #671, #674). - Generic and trait flow keeps more type information: Instantiated receivers, generic fields, generic methods,
list[Self], trait/supertrait upcasts, imported prost oneofs, explicit generic cycles, and static factory locals now survive typechecking and lowering more consistently (#237, #231, #253, #230, #184, #218, #279, #252, #255). - Generic receiver methods inherit defaults: Calls on instantiated generic classes and models use the same source default arguments as non-generic receiver calls instead of emitting too few Rust arguments (#731).
- Assert comparisons share expression emission:
assert_eq,assert_ne, and assert-statement comparison desugaring now use the ordinary binary-operation plan, so string comparisons in loops follow the same borrowed/owned behavior asifexpressions (#739). - Union widening preserves generated wrapper identity: Values typed as a narrower anonymous union can flow into wider compatible union targets, and flattened nested union aliases can narrow back through alias patterns, fallback bindings, or fallback subject helper calls without leaking mismatched Rust enum types (#741, #743).
- Runtime-boundary errors are clearer:
std.regextext borrowing, collection f-string formatting, and collection/string conversion diagnostics fail closer to the Incan source instead of surfacing as obscure Rust/runtime errors (#624, #625, #71, #81). - Reflection is capability-backed: Generic value reflection and explicit type-argument reflection infer the right generated Rust bounds, primitive type arguments expose stable source names, and value-position type names lower to explicit
Type[T]tokens instead of leaking Rust type paths (#712, #714, #715, #750). - Enum patterns use enum-owned metadata: Qualified enum patterns now read variant payloads from the enum's semantic metadata instead of ambient variant symbols, keeping imported stdlib enums stable when project enums reuse variant names (#710).
- Stringly compiler behavior is guarded: Remaining semantic string comparisons in high-risk compiler paths are fingerprinted so new string-based behavior has to be centralized or explicitly classified.
Rust Interop And Generated Rust¶
- Borrowing decisions are metadata-backed: Borrowed
str/bytes calls, method fallback borrowing, and retained generated imports now follow the same decisions across typechecking, lowering, and emission. - Rust bridge identity is preserved: Inspected methods with unknown generic or lifetime placeholders and re-exported Rust argument displays keep stable bridge identity, including nested generic wrappers such as
Arc<T>(#645, #630, #705). - Rust callable aliases can accept Incan closures: Rust
typealiases such asArc<dyn Fn(&[T]) -> Result<U, E> + Send + Sync>now preserve their alias target metadata, contextually type Incan closure parameters, follow alias chains such asScalarFunctionImplementation -> SliceCallback -> Arc<dyn Fn(...)>, and emit the required Rust closure parameter annotations without pulling heavyweight downstream crates into compiler regression tests (#708, #733). - Collection adaptation is less manual: Owned Incan values can flow to shared borrowed generic Rust parameters, and
list[T]can adapt toVec<U>where metadata proves the boundary (#506, #128). - Protobuf-style APIs need fewer workarounds: Prost-style inherent and trait-provided
decode<T: Buf>(buf: T)calls lower correctly (#609, #612). - Generated Rust pruning is safer: Enum-pattern imports and metadata-derived extension-trait imports survive pruning, while unused generated Rust is pruned without broad
allowattributes (#459, #447, #214). - Generated manifests stay smaller: Tokio and
serde_jsonstay behind feature gates, and generated helper stubs use named helpers (#351, #157). - Trait annotation failures are Incan diagnostics: Trait-typed local annotations now produce diagnostics instead of obscure lowering or generated-Rust failures (#462).
Multi-File And Packages¶
- Cross-module codegen is more predictable: Imported defaults qualify correctly, same-shaped union wrappers are shared, wide union narrowing lowers fully, keyword-named modules escape consistently, and public submodule reexports work under
src/(#395, #457, #461, #458, #122, #287). - Web registration keeps private internals private: Private route handlers and models are retained for web registration without making them user-visible public API (#117).
- Package exports match ordinary builds: Public aliases, public partial presets, package-boundary alias consumption, lowercase exported statics, imported static decorator strings, and keyword-named public symbols follow the same rules across build modes (#617, #631, #633, #658, #659, #698).
- Partial presets keep their defaults in decorators: Imported public partials now retain their projected default arguments and module-owned default symbols when used inside decorator factory arguments, matching ordinary runtime calls (#698, #701).
- Decorator metadata crosses package boundaries: Source signatures, imported/decorator
const strarguments, generic decorator factories, method-call decorator factories, and reexport-only facade projections are represented in checked metadata more reliably (#636, #638, #640, #669, #694, #695). - Decorator helpers can inspect generic callables:
func.__name__works in generic(F) -> Fdecorator helpers, including imported alias and union callable signatures, so registry decorators can infer the decorated helper name instead of repeating it as a string (#694, #701). - Decorated wrappers preserve defaults: Decorated functions and methods keep source default-argument call behavior when the final decorated callable surface still matches the original declaration, including direct imports and public facade re-exports (#703).
- Stdlib implementation modules stay internal: Generated stdlib source dependencies no longer leak unimported helper classes into project modules, so explicit sibling imports keep precedence over unrelated
std.*imports (#710). - Vocab expression-list clauses preserve item metadata:
ClauseSurface::expr_list(...)acceptsexpr as aliasentries and declared trailing item modifiers such asexpr for target with context, exposing structured metadata to desugarers instead of forcing SQL-shaped projections through field-set syntax (#724). - Expression-desugaring vocab declarations work as values:
DeclarationSurface::desugars_to_expression()blocks can now appear where expressions are valid, including assignment values and return values; colon and brace forms desugar before typechecking while preserving inline clause bodies, expression-list item metadata, compound clause tokens such asGROUP BY, and public vocab method-call output (#727). - Dependency vocab stays active under tests and formatting:
incan testandincan fmtnow parse dependency-activated DSL surfaces during collection, batch planning, and formatter parsing, matching ordinary--checkbehavior for the same test file (#730, #756). - Vocab helper calls use ordinary public call planning:
IncanExpr::Helper(...)output now follows the same exported-default, union-wrapping, owned-string, and dependency-owned type identity rules as directpub::librarycalls, so DSL desugarers can call source-backed helpers without hand-authored workarounds (#729). - Vocab-generated generic calls keep context: Expression-position desugar output now threads expected return types through generic function, callable, and method calls, so DSL-generated method calls infer the same output types as equivalent source calls (#735).
- Dependency-owned union wrappers stay provider-owned: Public dependency helpers and methods whose signatures use provider-owned union aliases now emit provider-qualified wrapper paths at consumer call boundaries instead of inventing crate-local anonymous union wrappers (#755).
- Rust metadata prewarm is observable and less scan-heavy: Rust inspection prewarm now reports explicit progress during long library builds, indexes definition-path aliases instead of scanning every cached item for re-export lookups, and flushes disk-cache updates once per batch (#736).
- Rust raw identifier fields emit correctly: Rust-backed fields whose real Rust name is a keyword can be accessed with the keyword spelling, such as
value.typeandTypeName(type=value), while generated Rust emits the actual raw identifier access such asvalue.r#type(#725). - Script and test manifests are scoped: Generated Cargo manifests include only reachable dependencies instead of blindly inheriting package-level heavy dependencies (#665).
Formatter And Test Runner¶
- Formatter output preserves meaning: Tuple-unpack comprehensions, f-string debug markers, escaped f-string newlines, numeric spelling, qualified enum/constructor patterns,
mut, docstrings, trailing commas, logical-expression wrapping, and class trait-adoption wrapping now round-trip more safely (#615, #616, #235, #250, #264, #289, #247, #394, #484, #565). - Comprehension behavior is more complete:
?propagation works inside comprehensions, and collection/string conversion diagnostics are clearer when runtime coercion fails. - Inline tests keep file-local scope: Directory inline-test runs preserve each file's parser and import scope, conventional test batches split on imported-name collisions, and decorated functions named like builtins resolve to the source binding inside inline tests (#676, #677).
- The test harness reuses more correctly:
incan testreuses generated harness state, isolates single-file runs, keeps project cwd stable, and includes helper modules such asstd.resultwhen test files use helper-backed surfaces (#268, #269, #271, #288, #378, #610, #505).
Docs And Dependencies¶
- User docs are closer to Divio shape: Stdlib pages for graph, regex, logging, hash, UUID, tempfile, collections, encoding, compression, datetime, and related modules now separate reference contracts from how-to or explanation material (#284).
- Contributor docs name the important boundaries: Crate boundaries, ownership metadata, staged Rust inspection, and the quarantined
std.webhost-runtime bridge are documented for maintainers (#284). - Dependency alerts are closed out: The
atomic-polyfilladvisory path is removed, Wasmtime/WASI and MSRV move together, Dependabot alerts are remediated across docs-site Python pins, VS Code lockfiles, Rust lock entries, and GitHub Actions, andpymdown-extensionsis pinned to10.21.3forGHSA-62q4-447f-wv8h(#260, #475, #464).
Known limitations¶
- Decimal arithmetic is not yet general language behavior. The
0.3decimal surface covers typed annotations, literal validation, formatting, generated Rust representation, and display; arithmetic semantics need a follow-up language/library decision. incan fmtis still conservative. RFC 053 gives vertical spacing and common wrapping rules, but it is not a general pretty-printer overhaul for every nested expression shape.std.regexis a safe-default regular-expression surface, not a Python/PCRE compatibility layer. Lookaround, pattern backreferences, and other backtracking-only features are tracked separately by RFC 100 for a futurestd.resurface.- Native Windows filesystem behavior is not part of the
0.3contract.std.fsdocuments Unix-like host behavior until the stdlib has an explicit platform split.
RFCs implemented¶
Language and compiler¶
- RFC 004: async fixtures
- RFC 006: Python-style generators
- RFC 009: numeric type system and builtin type registry
- RFC 016:
loopandbreak <value>loop expressions - RFC 017: validated newtypes with implicit coercion
- RFC 018: language primitives for testing
- RFC 024: extensible derive protocol
- RFC 025: multi-instantiation trait dispatch
- RFC 028: trait-based operator overloading
- RFC 029: union types and type narrowing
- RFC 032: value enums with
strandintbacking values - RFC 036: user-defined decorators
- RFC 038: variadic args and unpacking
- RFC 039:
racefor awaitable concurrency - RFC 043: Rust trait implementation from Incan
- RFC 044: open-ended trait methods
- RFC 046: computed properties
- RFC 049:
if letandwhile letpattern control flow - RFC 050: enum methods and enum trait adoption
- RFC 053: formatter vertical spacing buckets
- RFC 057: targeted Rust lint suppression for generated code
- RFC 068: protocol hooks for core language syntax
- RFC 069:
list.repeathelper for fixed-length initialization - RFC 070: result combinators for
Result[T, E] - RFC 071: pattern alternation in
matchandif let - RFC 083: symbol and method aliases
- RFC 084: RHS partial callable presets
- RFC 088: iterator adapter surface
Standard library¶
- RFC 010: Python-style
tempfilestandard library - RFC 030:
std.collectionsextended collection types - RFC 047: lightweight directed graph types
- RFC 051:
JsonValueforstd.json - RFC 055:
std.fsfilesystem APIs - RFC 056:
std.iobyte streams and binary parsing helpers - RFC 058:
std.datetimetemporal values, intervals, and runtime timing - RFC 059:
std.regexregular expressions, matches, captures, and replacement - RFC 060:
std.uuidparsing, generation, and formatting - RFC 061:
std.compressioncodec-based compression and decompression - RFC 064:
std.encodingbinary-text encoding and decoding utilities - RFC 065:
std.hashstable hashing primitives - RFC 072:
std.logginglogger acquisition, configuration, and structured events - RFC 101:
std.collections.OrdinalMapdeterministic key-to-ordinal lookup