Ownership inspect
Ask the compiler who owns a binding and who borrows it at a specific source line — at compile time, with zero runtime cost. Rust’s stable toolchain has no equivalent today (rustc reports errors but does not expose live borrow-state snapshots for a variable on demand).
Why compile-time, not runtime?
Ownership and borrowing exist only in the compiler’s borrowck phase. They are erased before LLVM IR — there is no ownership table in the binary. A hypothetical name.who_owns_me() runtime method could not return real data.
Nyra instead surfaces the same state the borrow checker already computes:
- moved — was ownership transferred?
- active_borrows — who holds
&/&muton this binding? - NLL — borrows end at the last use of the reference, not at
}
Pipeline hook (same walk as borrow checking):
parse → typecheck → borrowck (+ inspect collector) → lifetimes → codegen
Related: Ownership UX (auto-borrow, Swift-style errors) · Escape analysis (nyra build --verbose).
nyra inspect
Job: Snapshot one binding at a file line while learning ownership or debugging a borrow error.
nyra inspect NAME --at FILE:LINE [PROJECT]
# examples
nyra inspect name --at main.ny:42
nyra inspect name --at main.ny:42 .
nyra inspect count --at src/app.ny:18 .
NAME— binding to inspect (e.g.name,user).--at FILE:LINE— location of a statement on that line (1-based).PROJECT— project directory or single.nyfile (default.).
Snapshot is taken after the statement on that line executes in the borrow-check walk (so let name = … on line 42 shows name as valid).
Sample output (Move)
When the binding owns a Move value (let b = a), the report shows an ownership chain and current owner:
myname2 (string) @ main.ny:5 in main
ownership chain:
name ──move──► myname ──move──► myname2
you inspect: `myname2`
current owner: `myname2` (owns value)
kind: Move
binding: owned (valid)
moved: no
moved from: `myname`
borrowed by: (none)
drop: auto-drop at scope exit (line 12)
Inspecting an earlier binding (e.g. name at line 5) still shows the full chain and points to myname2 as the current owner.
Sample output (Borrow)
When the binding is a reference (let r = &name), it is a borrower — not the heap owner. The report shows a borrow chain and heap owner instead of move edges:
myname2 (&&string) @ main.ny:11 in main
you inspect: `myname2` (borrower)
heap owner: `name` (owns Move value)
borrow chain:
name ◄──borrow── myname ◄──borrow── myname2
kind: Copy (reference)
binding: valid (borrow)
moved: no
borrows from: `myname` (&)
borrowed by: (none)
CLI output is color-coded when stdout is a TTY: green for heap owner, yellow for borrowers and move edges, cyan for borrow edges and paths.
nyra check --ownership-verbose
Job: Summary of every binding at function exit (like escape analysis’s per-binding report).
nyra check .
nyra check . --ownership-verbose
Example stderr:
Checking ownership
ownership: 2 binding(s) at function exit
ownership: main::name → Move (valid)
ownership: main::r → Copy (valid)
Pairs well with nyra build --verbose (escape analysis) when tuning performance and memory.
Report fields
| Field | Meaning |
|---|---|
ownership chain | Move path: root ──move──► … ──move──► tip (Move bindings only) |
borrow chain | Borrow path: heap_owner ◄──borrow── … ◄──borrow── tip (ref bindings only) |
you inspect | The binding you queried; labeled (borrower) for ref bindings |
current owner | Who holds the Move value now (follows move edges; Move bindings only) |
heap owner | Root binding that owns the heap value behind a borrow chain (ref bindings only) |
kind | Copy or Move; ref bindings show (reference) |
binding | owned (valid), valid (borrow), moved (invalid), or not in scope |
moved | Whether the binding was moved; may note callee (into save()) |
moved from | Source of a move assignment (let dest = src); never shown for ref bindings |
borrows from | Immediate source of a ref binding (let r = &name → name) |
borrowed by | Active & / &mut holders (named ref or <expr> for temporaries) |
expires after line | NLL end of borrow (last use of the ref binding) |
drop | Auto-drop at scope exit for Move types still valid |
Move vs borrow
nyra inspect distinguishes owner and borrower roles:
| Pattern | Role | Chain shown | Owner field |
|---|---|---|---|
let b = a (Move type) | owner / moved-away | ownership chain | current owner |
let r = &a | borrower | borrow chain | heap owner |
Reusing the same variable names in one function (shadowing) clears stale move edges — a borrow inspect at line 11 will not show moved from from an earlier move block.
Worked examples
Move chain
fn main() {
let name = "Nyra"
let myname = name
let myname2 = myname
print(myname2) // line 5 — inspect myname2 (move)
}fn main() -> void {
let name: string = "Nyra"
let myname = name
let myname2 = myname
print(myname2) // line 5 — inspect myname2 (move)
}$ nyra inspect myname2 --at main.ny:5
ownership chain:
name ──move──► myname ──move──► myname2
current owner: `myname2` (owns value)
Borrow chain
fn main() {
let name = "Nyra"
let myname = &name
let myname2 = &myname
print(&myname2) // line 5 — inspect myname2 (borrow)
}fn main() -> void {
let name: string = "Nyra"
let myname = &name
let myname2 = &myname
print(&myname2) // line 5 — inspect myname2 (borrow)
}$ nyra inspect myname2 --at main.ny:5
you inspect: `myname2` (borrower)
heap owner: `name` (owns Move value)
borrow chain:
name ◄──borrow── myname ◄──borrow── myname2
NLL (borrow expiry)
fn main() {
let name = "Ada"
let r = &name
print(r) // line 4 — inspect name here
print(name) // line 5 — borrow from r has ended (NLL)
}fn main() -> void {
let name: string = "Ada"
let r = &name
print(r) // line 4 — inspect name here
print(name) // line 5 — borrow from r has ended (NLL)
}$ nyra inspect name --at main.ny:4
borrowed by: `r` (&string), expires after line 4
$ nyra inspect name --at main.ny:5
borrowed by: (none)
Limitations
- Requires a successful typecheck (same as
nyra check). - Line must contain a statement; blank lines and lone
}are not valid targets. - Inspects the binding in the current function’s borrow state (nested blocks share the same function frame).
- Not a runtime API — no reflection in the binary; use
nyra inspector IDE integration (planned: LSP hover). - Rust comparison:
cargo/rustcexplain errors; Nyra additionally exposes proactive ownership snapshots for learning and debugging.