Borrow Checking
RukaLang now tracks local borrows with a place-based checker that is smaller than Rust borrowck but follows the same safety shape for overlapping access.
Scope and Goals
- Keep reference semantics simple for MVP.
- Support temporary local references to named bindings, struct fields, tuple fields, indexed elements, and slice ranges.
- Prevent overlapping mutable and shared access to the same storage region.
- Avoid lifetime inference complexity by keeping references local-only (no storing in user data structures and no returning references).
Surface Forms Covered
let x = place_exprcreates a shared local reference whenplace_expris a place expression.let &x = place_exprcreates a mutable local reference.- Existing call-argument borrow forms remain available (
&argfor&Tparameters).
For non-place initializers, plain let x = expr continues to behave like a
normal value initialization.
Place Model
The checker resolves borrowable expressions into a canonical place path:
- root binding name (
x) - zero or more projections:
- field projection (
.field/ tuple index like.0) - index-like projection (
[i]and[a..b]both normalize to one index-like projection)
- field projection (
Examples:
pair.left->pair .field(left)xs[3]->xs .index_likexs[1..3]->xs .index_likepair.left.value->pair .field(left) .field(value)
Active Loans
Each scope keeps a list of active loans:
- loan kind: shared or mutable
- place path
- owner local (the local binding that introduced the loan)
Loans are introduced by local borrow declarations and removed when the owning scope exits.
Overlap Rule
Two places overlap when:
- they have the same root binding, and
- their projections are not proven disjoint.
Disjointness rule used today:
- field-vs-field at the same depth with different field names is disjoint
(
pair.leftvspair.right) - any case involving index-like projection is treated as overlapping (conservative)
- prefix/ancestor-descendant place relations overlap
This matches Rust's conservative behavior for array/slice indexing while still allowing independent struct-field borrows.
Enforced Access Rules
- read of a place is rejected if an overlapping mutable loan is active
- write/move of a place is rejected if any overlapping loan is active
- creating a shared loan is rejected if an overlapping mutable loan is active
- creating a mutable loan is rejected if any overlapping loan is active
Current Limits
- No index disjointness proof (
xs[0]vsxs[1]is still overlapping). - No borrow splitting API yet (Rust-like
split_at_mutequivalent not present). - Checker is lexical-scope based; it does not perform advanced non-lexical lifetime shortening.
These limits are intentional for MVP simplicity.