Ownership Model
DUUMBI implements a Rust-inspired ownership model with borrow checking enforced at the graph validation level — before compilation even begins. Every heap value (string, array, struct) has exactly one owner.
Core rules
Section titled “Core rules”- Single owner — every value has exactly one owner at a time
- Move semantics — assignment transfers ownership (the original becomes invalid)
- Borrowing — you can borrow a reference (
&T) without taking ownership - Exclusive mutation — mutable borrows (
&mut T) are exclusive (no other borrows allowed) - Scope cleanup — all owned values must be
Dropped before scope exit
Ownership lifecycle
Section titled “Ownership lifecycle”Alloc ──→ owner is valid │ ├──→ Borrow ──→ &T exists (read-only, multiple OK) │ │ │ └──→ borrow ends (reference no longer used) │ ├──→ BorrowMut ──→ &mut T exists (exclusive, no other borrows) │ │ │ └──→ borrow ends │ ├──→ Move ──→ new owner (original invalidated) │ └──→ Drop ──→ value deallocatedBorrow checker rules
Section titled “Borrow checker rules”Rule 1: No use after move
Section titled “Rule 1: No use after move”Once a value is moved, the original variable is invalidated. Any access after Move triggers E021.
[ { "@type": "duumbi:Alloc", "@id": "…/0", "duumbi:value": { "@type": "duumbi:ConstString", "value": "hello" } }, { "@type": "duumbi:Move", "@id": "…/1", "duumbi:source": { "@id": "…/0" }, "duumbi:target": "new_var" }, { "@type": "duumbi:Print", "@id": "…/2", "duumbi:value": { "@id": "…/0" } }] ❌ E021: use-after-move "…/0" was moved at "…/1", cannot access at "…/2"Fix: Borrow instead of move, or access before the move.
Rule 2: Exclusive mutable borrows
Section titled “Rule 2: Exclusive mutable borrows”You cannot have a &mut T while any other borrow (&T or &mut T) exists. Violating this triggers E022.
[ { "@type": "duumbi:Alloc", "@id": "…/0", "duumbi:value": { "@type": "duumbi:ArrayNew", "duumbi:element_type": "i64" } }, { "@type": "duumbi:Borrow", "@id": "…/1", "duumbi:source": { "@id": "…/0" } }, { "@type": "duumbi:BorrowMut", "@id": "…/2", "duumbi:source": { "@id": "…/0" } }] ❌ E022: borrow exclusivity violated Cannot create &mut borrow of "…/0" while &T borrow "…/1" is activeFix: End the immutable borrow before creating a mutable one (ensure …/1 is no longer referenced).
Rule 3: No move while borrowed
Section titled “Rule 3: No move while borrowed”Moving a value while borrows are active triggers E027.
Alloc ──→ value "s" │ ├──→ Borrow ──→ &s (still active) │ └──→ Move s ──→ ❌ E027: cannot move "s" while "&s" existsFix: Drop all borrows before moving the owner.
Rule 4: No dangling references
Section titled “Rule 4: No dangling references”A reference must not outlive the value it borrows. If the source is dropped while references exist, E026 fires.
Alloc ──→ value "data" │ ├──→ Borrow ──→ &data │ └──→ Drop "data" ──→ ❌ E026: "&data" is now danglingFix: Drop references before dropping the source value.
Rule 5: Complete cleanup
Section titled “Rule 5: Complete cleanup”Every owned value must be dropped before scope exit. Missing Drop triggers E024.
[ { "@type": "duumbi:Alloc", "@id": "…/0", "duumbi:value": { "@type": "duumbi:ConstString", "value": "hello" } }, { "@type": "duumbi:Return", "@id": "…/1", "duumbi:value": { "@type": "duumbi:Const", "value": 0 } }] ❌ E024: drop incomplete Owned value "…/0" not dropped before scope exit at "…/1"Fix: Add { "@type": "duumbi:Drop", "duumbi:value": { "@id": "…/0" } } before the return.
Ownership ops
Section titled “Ownership ops”| Op | Description | Runtime cost |
|---|---|---|
Alloc | Allocate a new value (type-specific) | Allocation |
Move | Transfer ownership | No cost (pointer copy) |
Borrow | Create &T (immutable reference) | No cost (pointer copy) |
BorrowMut | Create &mut T (mutable reference) | No cost (pointer copy) |
Drop | Deallocate (type-specific duumbi_*_free) | Deallocation |
Reference types
Section titled “Reference types”&T— immutable reference (e.g.,&string,&array<i64>)&mut T— mutable reference (e.g.,&mut array<i64>)
References are zero-cost at runtime (pointer copies). The borrow checker validates them at graph validation time.
Error handling types
Section titled “Error handling types”DUUMBI also provides Rust-inspired error handling types that integrate with the ownership model:
| Type | Description |
|---|---|
result<T, E> | Success (Ok(T)) or failure (Err(E)) |
option<T> | Some value (Some(T)) or nothing (None) |
Related ops: ResultOk, ResultErr, ResultIsOk, ResultUnwrap, OptionSome, OptionNone, OptionIsSome, OptionUnwrap, Match.
Error codes E030–E035 enforce exhaustive error handling — unhandled Result or Option values are compile-time errors.
All ownership error codes
Section titled “All ownership error codes”| Code | Error | Quick fix |
|---|---|---|
| E020 | Single owner | Use Move or Borrow, not raw copy |
| E021 | Use after move | Access before move, or borrow instead |
| E022 | Borrow exclusivity | End other borrows first |
| E023 | Lifetime exceeded | Shorten the reference scope |
| E024 | Drop incomplete | Add Drop before scope exit |
| E025 | Double free | Remove duplicate Drop |
| E026 | Dangling reference | Drop refs before source |
| E027 | Move while borrowed | Drop borrows before move |
| E028 | Lifetime param missing | Add lifetime to function sig |
| E029 | Return lifetime mismatch | Fix return lifetime annotation |
See the full Error Codes reference for all codes and examples.