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:

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 .

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

FieldMeaning
ownership chainMove path: root ──move──► … ──move──► tip (Move bindings only)
borrow chainBorrow path: heap_owner ◄──borrow── … ◄──borrow── tip (ref bindings only)
you inspectThe binding you queried; labeled (borrower) for ref bindings
current ownerWho holds the Move value now (follows move edges; Move bindings only)
heap ownerRoot binding that owns the heap value behind a borrow chain (ref bindings only)
kindCopy or Move; ref bindings show (reference)
bindingowned (valid), valid (borrow), moved (invalid), or not in scope
movedWhether the binding was moved; may note callee (into save())
moved fromSource of a move assignment (let dest = src); never shown for ref bindings
borrows fromImmediate source of a ref binding (let r = &namename)
borrowed byActive & / &mut holders (named ref or <expr> for temporaries)
expires after lineNLL end of borrow (last use of the ref binding)
dropAuto-drop at scope exit for Move types still valid

Move vs borrow

nyra inspect distinguishes owner and borrower roles:

PatternRoleChain shownOwner field
let b = a (Move type)owner / moved-awayownership chaincurrent owner
let r = &aborrowerborrow chainheap 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)
}
$ 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)
}
$ 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)
}
$ 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