RFC 008: Const Bindings¶
Status: Done
Created: 2025-12-10
Implemented: 2025-12-23
Summary¶
Introduce explicit constant bindings in Incan using the industry-standard declaration form:
const PI: float = 3.14
const HOST = "127.0.0.1" # type may be inferred when obvious
The goal is to let users declare compile-time constants with clear intent and to allow codegen to emit Rust const where possible.
Goals¶
- Provide a clear, conventional syntax (
const NAME [: Type] = <const-expr>). - Enforce const-evaluable expressions at compile time (reject runtime-dependent values).
- Generate Rust
constbindings when allowed; fall back to errors rather than silently downgrading. - Keep scope simple initially (module-level constants).
Non-Goals (initial)¶
- Block-level
constsemantics (can be added later). static/static mutsupport.- Inline const expressions (e.g.,
const_len(...)) or const generics. - Complex const-eval (function calls, IO, random, runtime-derived values).
Syntax¶
Module-level bindings:
const PI: float = 3.14159
const APP_NAME: str = "incan"
const MAX_RETRIES: int = 5
# With type inference when the literal is unambiguous
const DEFAULT_PORT = 8080
Rules (initial):
- Required initializer.
- Optional type annotation; if omitted, the type must be inferrable from the literal.
- Only allowed at module scope (phase 1).
Const-Evaluable Expressions (phase 1)¶
Allowed:
- Literals: int, float, bool, str, bytes.
- Simple unary/binary ops on const literals:
+,-,*,/,%,&&/||, comparisons, string concatenation ("a" + "b"). - Tuple/list/dict/set literals whose elements/keys/values are themselves const-evaluable.
Disallowed (initial):
- Function/method calls (including builtins).
- Comprehensions, ranges, f-strings.
- Accessing variables, environment, or runtime state.
- Imports resolved at runtime.
If an expression is not const-evaluable, the compiler should emit a clear error at the binding site.
Codegen¶
- Emit Rust
constfor accepted const bindings. - For string/bytes literals, emit as
&'staticwhere appropriate; otherwise useconstwith owned types that areconst-constructible. - If the expression cannot be represented as a Rust
const, fail the build with an actionable error (no silent fallback tolet).
Typechecker¶
- Enforce const-evaluable expression rules.
- Ensure the inferred/annotated type matches the initializer.
- Restrict scope to module-level for phase 1.
Future Work (not in scope here)¶
- Block-level
const. static(single-instance data) and safety rules.- Inline const expressions / const-eval helpers.
- Allowing a limited set of pure builtins in const context (e.g., length of const literals).
Examples¶
const VERSION: str = "0.1.0"
const DEFAULT_TIMEOUT_SECS: float = 2.5
const GREETING = "hello" + " world"
const PORTS = [80, 443]
const HEADERS = {"User-Agent": "incan"}
Invalid (should error):
const NOW = time.now() # function call
const HOST = env("HOST") # runtime dependency
const STEP = 1 + x # depends on non-const symbol
Checklist¶
- [x] Lexer/parser support for
constbinding declarations. - [x] AST node for const bindings (module scope).
- [x] Typechecker: const-eval rules and errors for non-const expressions.
- [x] Codegen: emit Rust
const; error on non-representable expressions. - [x] Docs: guide section + examples.
- [x] Tests: parser/typechecker/codegen coverage.