Scope and status
링크Use this page to declare bindings and understand name lookup, assignment, and lexical capture.
Types covers compatibility and shared inference. Functions and generics covers calls and generic specialization. Pattern forms, module lookup, and protocol resolution belong to their respective chapters.
Declaration syntax
링크NamedLet = "let" "mut"? Identifier (":" Type)? "=" Expression ;Const = "const" Identifier (":" Type)? "=" Expression ;Assignment = AssignmentTarget "=" Expression ;AssignmentTarget = Identifier | MemberAccess | IndexAccess ;
This transcribed excerpt shows named declarations and plain assignment. Pattern forms, compound operators, and statement boundaries have separate grammar productions.
Declaration source observations
링크Binding declaration forms
Each declaration requires an initializer after = and permits a type annotation. A named binding accepts at most one annotation, including an exported binding. Repeating the annotation produces TPZ2001 during parsing.
| Declaration | Binding form |
|---|---|
let | A pattern |
let mut | A single name |
const | A single name |
이유
An initializer gives every binding a value. One type annotation supplies one expectation without a second spelling or a competing annotation for the same name.
Binding mutability
| Declaration | Binding mutability |
|---|---|
let | Immutable |
let mut | Mutable |
const | Immutable |
이유
Reassignment is an explicit choice in the declaration. Immutable bindings let readers rely on one assigned value.
Literal const initializers
Integer literals and untagged string literals without interpolation satisfy the constant-expression requirement for const initializers.
이유
A literal supplies its value without consulting runtime state, so it can be used wherever a constant expression is required.
Const references to let bindings
A const initializer must be a constant expression. Referencing an ordinary let or let mut binding is rejected with TPZ5001.
이유
An immutable runtime binding can still depend on runtime computation. Rejecting such references keeps constant-expression admission independent of runtime state.
Reserved None binding name
None cannot be a binding name. This includes let mut, const, typed binding patterns, and bare untyped patterns. The parser reports TPZ2012. Write None() for the Option constructor in patterns and values.
이유
Reserving None keeps absence recognizable in expressions and patterns. A local name cannot silently replace the Option constructor.
Constant expressions and retained values
A const initializer must pass ordinary name and type checking and the constant-expression check. Admitted forms include scalar and Length literals, None(), untagged strings with constant interpolations, available constant bindings, function values, parentheses, unary expressions, binary expressions other than in, arrays without spread, and record literals whose fields are constant expressions. A reference to a let binding, an ordinary function call, or a later constant in the same module is rejected. Const makes the binding immutable. Its data follows collection value semantics. Grouping a callee preserves its operation and receiver. The grouping does not change whether the call is a constant expression.
The numeric call forms are int.and, int.or, int.xor, and int.shift with one constant integer operand, and float.trunc, float.floor, float.ceil, and float.round with no arguments and a constant float receiver. These calls take no explicit type arguments or spreads. Integer operands are supplied positionally. Integer and float arithmetic expressions may supply their receivers.
Compiler-recognized std.typeset.fromScaledPoints and fromMilliPoints calls, Length conversion methods toPt, toMm, toCm, toIn and toScaledPoints, Pt.toMilliPoints, and Em.resolve admit constant operands. The std.typeset constructor recognition requires the generated standard module. Normal type and argument checks still apply. The implementation issue below preserves the permitted function-value expectation. Initialization-pass ordering is not a new language timing guarantee.
이유
A constant must not depend on runtime state. Grouping a callee preserves its operation and receiver, so it must preserve the same constant-expression decision.
Assignment source observations
링크Collection values
Since 6.0.0, Array, Map, Set, and ByteBuffer are values. Assignment, argument passing, return, and immutable capture each produce an independent value. Storing a value in a record field, collection element, or Map key or value does the same. Changing one value leaves the others unchanged.
The rule applies recursively inside records, enum payloads, Option, and Result. ByteBuffer contains data without host state and follows the same rule.
A write requires one let mut root binding, including writes through nested fields, indices, and collection operations. An immutable binding's data cannot change through an alias. The immutable assignment rule governs the write path. A function cannot change a caller's collection through a parameter. Bind a mutable local and return the changed value when the caller needs it.
An implementation may share storage and copy when a write occurs. A program cannot observe this storage sharing. The rule makes no promise about allocation, time, or space costs.
Shared handles represent host resources. File retains its shared cursor and closed state. A mutable capture shares one variable. A closure's write to that variable is visible outside the closure. A value assigned to another variable before the write remains unchanged.
See immutable assignment, capture observation, parameter bindings, and collection keys. The existing File cursor example and closed handle example show the resource exception.
이유
Each intent has one representation. Data uses values and host resources use handles. Independent values prevent hidden changes, so results are reproducible and easy to verify. Concurrent work on separate collection values has no data race through shared collection storage. Collections map directly to value representations such as Rust Vec and HashMap.
Assignment target syntax
An assignment target must be an identifier, member access, or index access. Other expression shapes produce TPZ2003 during parsing. The target also undergoes receiver and mutability checks.
이유
Assignment must identify a location that can receive a value. A temporary result has no binding or element to update.
Assignment requires a mutable root
Direct replacement of an imported binding is rejected during resolution under the import assignment rule. After resolution succeeds, assignment to an immutable binding, or to a member or index rooted in that binding, is rejected with TPZ5003.
이유
Mutability follows the root binding through field and index access. Adding an access step must not bypass the declaration's mutation rule.
Expected type of assignment
For =, the target type is the expected type of the right-hand expression.
이유
The destination already determines which values an assignment can store. Reusing that type gives empty or generic values the context they need.
Assignment through optional access
A member or index assignment target whose receiver chain contains optional access ?. is rejected during checking with TPZ5001.
이유
An optional receiver can be absent, leaving no location to update. An explicit branch makes the skipped update and value evaluation visible.
Assignment through Map indexing
Assignment through a Map index is rejected with TPZ5001. Use the Map mutation API to update that binding's value under collection value semantics.
이유
Map insertion can add a key as well as replace a value. A named mutation operation makes that behavior explicit without another update spelling.
Scope and capture source observations
링크Lexical name lookup
Local name lookup searches active scopes from the innermost outward and selects the first matching binding. Value and type namespaces are searched separately.
이유
Lexical nesting determines what a name means without depending on the caller. Separate namespaces keep value lookup from selecting a type declaration.
Nested block scope
A nested block introduces a scope. After an inner binding is declared, later references inside the block resolve to it instead of an outer binding of the same name. References outside that block continue to resolve to the outer binding.
이유
A block bounds the visibility of local names. Temporary names inside it cannot replace the bindings used by surrounding code.
Local initializer scope
Local let, const names become visible after their initializers have been resolved. An existing outer binding of the same name remains visible in the initializer.
이유
The initializer can use an existing outer value while computing its replacement. The new name becomes usable after that value exists.
Unbound value names
An ordinary value identifier with no binding or built-in definition is rejected with TPZ5002. Namespace heads follow the module lookup rules.
이유
Every ordinary reference needs a declaration that determines its value. Static rejection prevents execution with an unspecified binding.
Direct top-level forward reads
A top-level let initializer that directly reads a later let binding in the same module is rejected with TPZ3018 during resolution, for both entry and imported files. Declaration order is the statement order in that module.
이유
A declaration cannot read a runtime value that has not been initialized. Resolving direct reads before execution catches this mistake with the same TPZ3018 identity in entry and imported files. Local unbound-name errors and delayed runtime reads retain their separate TPZ5002 contract.
Lexical capture identity
A function or lambda reference to an outer lexical binding in the same module resolves to that binding’s original declaration.
이유
A closure's references keep the meaning they had where it was defined. Calling it from a different scope must not retarget those names.
Values observed by existing and escaping closures
Closures that capture the same mutable binding observe its later assignments, including assignments made by another closure. An immutable capture retains its assigned value and cannot be rebound inside the closure. Collection values follow value semantics. Captured values and shared bindings remain available after the creating function returns while a closure retains them.
이유
A mutable captured binding represents one shared variable. Retaining captured storage while closures exist prevents references from outliving their values.
Initialization through branches, calls, and closures
Module-level let bindings become ready after their initializers complete, and function bindings become ready at their declaration position. A direct initializer read of the same or a later runtime binding is rejected with TPZ3018 during resolution in entry and imported files alike. The direct check follows an if or while condition, a match scrutinee, a for iterator, and the first comprehension source. It does not enter function, lambda, branch, arm, or loop bodies. Short-circuit right operands and optional-call arguments are delayed as well. A delayed reference reads the binding when execution reaches it. Reading before initialization faults with TPZ5002; reading after initialization returns the current value. Merely storing a closure does not evaluate its body.
이유
Module initialization can perform effects, so source order determines when runtime values become available. Delayed code must check the value when it actually executes.
Redeclaration and nested shadowing
A module-level name may be declared once. Let, const, function, type, and imported declarations participate in the same module name table, and a second declaration is rejected with TPZ3008. Reusing a name in a nested scope is shadowing. A repeated local const is rejected with TPZ5008. The same local scope must also reject a second let or let mut name; the current let pattern path bypasses the checker guard, which is recorded as an implementation limitation. The local let regression retains TPZ5008 as its intended rejection contract.
이유
A name should identify one declaration within a scope. Rejecting duplicates prevents silent replacement, while nested scopes make shadowing explicit.
Examples and expected results
링크Each example is a complete example. file. The expected success or rejection is independent of future observed output. All examples remain Pending. A source-backed expectation is not an executed result. Negative examples specify the intended cause and phase; their exact diagnostic locations remain unmeasured.
Immutable, mutable, and constant bindings
링크import std.iolet start: int = 1let mut count: int = startcount = 2const label = "Topaz"io.writeLine("{label}:{start}:{count}")
Topaz:1:2
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
Topaz:1:2
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
Topaz:1:2
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · stdout matches the authored expectation
- 통과 · stdout matches the authored expectation
- 통과 · stdout matches the authored expectation
- 통과 · stdout matches the authored expectation
An immutable binding cannot be reassigned
링크let count: int = 1count = 2
표준 출력 없음.error[TPZ5003]: `count` is immutable; declare it with `let mut`
--> example.tpz:2:1
|
2 | count = 2
| ^^^^^
example.tpz: 1 type diagnostic
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ5003]: `count` is immutable; declare it with `let mut`--> example.tpz:2:1|2 | count = 2| ^^^^^example.tpz: 1 type diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ5003; cause bindings-assignment-to-immutable; primary location asserted; additional diagnostics allow
- 통과 · checking TPZ5003; cause bindings-assignment-to-immutable; primary location asserted; additional diagnostics allow
An ordinary let reference is not a const initializer
링크let source: int = 1const frozen: int = source
표준 출력 없음.error[TPZ5001]: `const` initializers must be constant expressions (§4)
--> example.tpz:2:21
|
2 | const frozen: int = source
| ^^^^^^
example.tpz: 1 type diagnostic
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ5001]: `const` initializers must be constant expressions (§4)--> example.tpz:2:21|2 | const frozen: int = source| ^^^^^^example.tpz: 1 type diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ5001; cause bindings-const-runtime-reference; primary location asserted; additional diagnostics allow
None cannot name a mutable binding
링크let mut None = 1
표준 출력 없음.error[TPZ2012]: `None` is the Option constructor (§22.1) and cannot be a binding name
--> example.tpz:1:9
|
1 | let mut None = 1
| ^^^^
example.tpz: 1 diagnostic
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ2012]: `None` is the Option constructor (§22.1) and cannot be a binding name--> example.tpz:1:9|1 | let mut None = 1| ^^^^example.tpz: 1 diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ2012; cause bindings-none-name; primary location asserted; additional diagnostics allow
A nested binding shadows the outer one
링크import std.iolet value: int = 1let nested: string = {let value: string = "inner"value}let original: int = valueio.writeLine("{nested}:{original}")
inner:1
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
inner:1
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
inner:1
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · stdout matches the authored expectation
An ordinary unbound name is rejected
링크let copied: int = missing
표준 출력 없음.error[TPZ5002]: `missing` is not bound
--> example.tpz:1:19
|
1 | let copied: int = missing
| ^^^^^^^
example.tpz: 1 type diagnostic
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ5002]: `missing` is not bound--> example.tpz:1:19|1 | let copied: int = missing| ^^^^^^^example.tpz: 1 type diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ5002; cause bindings-unbound-read; primary location asserted; additional diagnostics allow
A later top-level let is unavailable to a direct initializer
링크let before: int = afterlet after: int = 1
표준 출력 없음.기대 결과
The direct module initializer uses the same TPZ3018 resolve contract in entry and imported files. Current SELF rejects this entry source with TPZ5002; the existing receipt remains historical evidence for that earlier identity. The expected rejection and location are retained, and only the diagnostic assertion is limited.
A lambda refers to an outer binding
링크import std.iolet anchor: int = 7let reader: (int) -> int = (value) => anchorlet observed: int = {let anchor: int = 99reader(0)}io.writeLine("{observed}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
7
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
Two escaping closures share one mutable binding
링크import std.iofunction makeCounters() -> { advance: () -> int, read: () -> int } {let mut count: int = 0let advance: () -> int = () => {count += 1count}let read: () -> int = () => countcount = 4{ advance: advance, read: read }}let counters = makeCounters()io.writeLine("{counters.read()}:{counters.advance()}:{counters.read()}")
4:5:5
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
4:5:5
Immutable captures retain independent collection values
링크import std.iofunction makeReader() -> () -> string {let mut current: int = 1let snapshot: int = currentlet items: Array<int> = [10]let mut alias: Array<int> = itemslet read: () -> string = () => "{current}:{snapshot}:{items[0]}"current = 2alias[0] = 30read}let read = makeReader()io.writeLine(read())
2:1:10
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2:1:10
A closure cannot reassign an immutable capture
링크function makeReader() -> () -> int {let count: int = 1() => {count = 2count}}
표준 출력 없음.기대 결과
Call a stored closure after its later binding initializes
링크import std.iolet read: () -> int = () => laterlet later: int = 7io.writeLine("{read()}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
A skipped branch does not read a later binding
링크import std.iolet chosen: int = if (false) { after } else { 2 }let after: int = 7io.writeLine("{chosen}:{after}")
2:7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2:7
The condition is an immediate initializer read
링크let chosen: int = if (after) { 1 } else { 2 }let after: bool = true
표준 출력 없음.기대 결과
The direct module initializer uses the same TPZ3018 resolve contract in entry and imported files. Current SELF rejects this entry source with TPZ5002; the existing receipt remains historical evidence for that earlier identity. The expected rejection and location are retained, and only the diagnostic assertion is limited.
An executed branch faults before a later binding initializes
링크let chosen: int = if (true) { after } else { 0 }let after: int = 7
표준 출력 없음.기대 결과
An inner scope may reuse an outer name
링크import std.iolet value: int = 1let inner: int = {let value: int = 2value}io.writeLine("{value}:{inner}")
1:2
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1:2
Module declarations cannot reuse an existing name
링크let value: int = 1const value: int = 2
표준 출력 없음.기대 결과
A local let duplicate retains the intended static rejection
링크function invalid() -> int {let value: int = 1let value: int = 2value}
표준 출력 없음.기대 결과
기대 결과
거절 단계 check. 종료 코드가 0이 아닙니다.
bindings-local-same-scope-redeclaration at
example.tpz:3:9
Constant expressions retain independent collection values
링크import std.ioconst base: int = 2const bits: int = base.shift(1).or(1)const rounded: int = (2.2).ceil()const data = { label: "value-{base}", values: [base, bits] }let mut alias: Array<int> = data.valuesalias[0] = 9io.writeLine("{data.label}:{bits}:{rounded}:{data.values[0]}")
value-2:5:3:2
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
value-2:5:3:2
A constant cannot read a later same-module constant
링크const before: int = afterconst after: int = 7
표준 출력 없음.기대 결과
A permitted named function value remains the expected constant value
링크import std.iofunction answer() -> int { 7 }const saved: () -> int = answerio.writeLine("{saved()}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
Store the function value after its declaration executes
링크import std.iofunction answer() -> int { 7 }let saved: () -> int = answerio.writeLine("{saved()}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
The local const path checks duplicate names
링크function invalid() -> int {const value: int = 1const value: int = 2value}
표준 출력 없음.기대 결과
기대 결과
거절 단계 check. 종료 코드가 0이 아닙니다.
bindings-local-same-scope-redeclaration at
example.tpz:3:11
Repeat a let annotation
링크let answer: int: int = 1
표준 출력 없음.기대 결과
Repeat an exported let annotation
링크export let answer: int: int = 1
표준 출력 없음.기대 결과
Repeat a mutable binding annotation
링크let mut answer: int: int = 1
표준 출력 없음.기대 결과
Repeat a constant annotation
링크const answer: int: int = 1
표준 출력 없음.기대 결과
Annotate each binding once
링크let local: int = 1export let published: int = locallet mut count: int = localconst label: string = "Topaz"export const publishedLabel: string = label
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
Group admitted numeric callees
링크const bits: int = ((5).and)(3)const rounded: int = ((2.2).ceil)()
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
Group admitted length callees
링크import std.typesetconst physical: Length<Pt> = ((1in).toPt)()const relative: Length<Pt> = ((2em).resolve)(12pt)
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
Use admitted calls and a grouped length constructor
링크import std.typesetconst bits: int = (5).and(3)const rounded: int = (2.2).ceil()const physical: Length<Pt> = (1in).toPt()const relative: Length<Pt> = (2em).resolve(12pt)const constructed: Length<Pt> = (typeset.fromMilliPoints)(1000)
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
An ordinary call remains a runtime computation
링크function answer() -> int { 3 }const direct: int = answer()
표준 출력 없음.기대 결과
Grouping does not make an ordinary call constant
링크function answer() -> int { 3 }const grouped: int = (answer)()
표준 출력 없음.기대 결과
Assignment keeps array values independent
링크import std.iolet a = [1, 2]let mut b = ab.push(3)io.writeLine("{a.length}:{b.length}")
2:3
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2:3
Return a changed local value from a function
링크import std.iofunction appended(values: Array<int>) -> Array<int> {let mut local = valueslocal.push(3)local}let items = [1, 2]let changed = appended(items)io.writeLine("{items.length}:{changed.length}")
2:3
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2:3
A saved record keeps its collection field
링크import std.iolet mut r = { items: [1, 2] }let saved = rr.items[0] = 5io.writeLine("{saved.items[0]}:{r.items[0]}")
1:5
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1:5
Insert and remove entries in an independent Map value
링크import std.iolet entries = map { "one": 1 }let mut changed = entrieschanged.insert("two", 2)io.writeLine("{entries.length}:{changed.length}")changed.remove("one")io.writeLine("{entries.keys[0]}:{changed.keys[0]}")
1:2
one:two
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1:2one:two
Add and remove members in an independent Set value
링크import std.iolet values = set { 1 }let mut changed = valueschanged.add(2)io.writeLine("{values.length}:{changed.length}")changed.remove(1)io.writeLine("{1 in values}:{1 in changed}")
1:2
true:false
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1:2true:false
A mutable capture changes one variable and preserves earlier values
링크import std.iolet mut items = [1]let before = itemslet append: () -> () = () => { items.push(2) }append()io.writeLine("{items.length}:{before.length}")
2:1
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2:1