Rust Ownership, the Borrow Checker (NLL), and Workaround Patterns TODO
Concept
Rust's ownership model gives every value a single unique owner, and the owner freeing its resources when it goes out of scope guarantees memory safety at compile time without a GC. The borrow checker adds to this a rule that disallows aliasing and mutation at the same time, enforcing that at any given moment there are either multiple immutable references or exactly one mutable reference, never both. NLL (Non-Lexical Lifetimes) computes a borrow's valid range not as the entire lexical scope but as the point in the control-flow graph where that reference is last used, letting code that's actually safe but used to be rejected compile. Even so, there are structures the checker can't prove safe — self-referential structures, cyclic graphs, shared mutable state — and for those you fall back to an arena that uses indices instead of references, Rc and RefCell that push the check to runtime, per-field split borrows, or, as a last resort, unsafe encapsulated behind a safe API.
When you build a node, an indexer, or a ZK tool in Rust, a large share of the time goes not into logic but into fighting the borrow checker, and knowing the workaround patterns lets you sidestep that fight at the design stage.
Code & Formula
# Rust 소유권·차용 검사기 우회 패턴 — RefCell류 런타임 검사와 arena(인덱스) 패턴을 파이썬으로 흉내낸다.
class BorrowError(Exception):
pass
class RefCell:
# aliasing XOR mutation 규칙(불변 차용 다수 OR 가변 차용 단 하나)을
# 컴파일 타임이 아니라 런타임에 검사로 옮긴 패턴
def __init__(self, value):
self._value = value
self._shared_borrows = 0
self._mut_borrowed = False
def borrow(self):
if self._mut_borrowed:
raise BorrowError("이미 가변 차용 중인데 불변 차용 시도")
self._shared_borrows += 1
return self._value
def release_borrow(self):
self._shared_borrows -= 1
def borrow_mut(self):
if self._mut_borrowed or self._shared_borrows > 0:
raise BorrowError("다른 차용이 있는데 가변 차용 시도")
self._mut_borrowed = True
def set(self, value):
if not self._mut_borrowed:
raise BorrowError("가변 차용 없이 값 변경 시도")
self._value = value
self._mut_borrowed = False
cell = RefCell(10)
v1, v2 = cell.borrow(), cell.borrow() # 불변 차용은 여러 개 동시에 허용
print("동시 불변 차용:", v1, v2)
try:
cell.borrow_mut() # 불변 차용이 살아있는데 가변 차용 시도 -> 위반
except BorrowError as e:
print("차단됨:", e)
cell.release_borrow(); cell.release_borrow()
cell.borrow_mut()
cell.set(20)
print("가변 차용 해제 후 값 변경 성공:", cell._value)
# arena(인덱스) 패턴: 참조 대신 정수 인덱스로 구조를 표현해 차용 문제 자체를 피한다
class Arena:
def __init__(self):
self.nodes = [] # (value, children_indices)
def add(self, value, children=()):
self.nodes.append((value, list(children)))
return len(self.nodes) - 1 # 인덱스를 "참조"처럼 반환
def sum_subtree(self, idx):
value, children = self.nodes[idx]
return value + sum(self.sum_subtree(c) for c in children)
arena = Arena()
leaf1 = arena.add(1)
leaf2 = arena.add(2)
root = arena.add(10, children=[leaf1, leaf2])
print("arena 트리 합:", arena.sum_subtree(root))
docs/code/algorithms/algorithms-30.py
Exercise
Build a reference-based tree or linked list, deliberately run into a compile failure, then reimplement the same structure once with Vec-index-based references and once with Rc<RefCell<..>>, and note what moved from a compile-time check to a runtime check.
Practical Connection
Ownership and aliasing rules remain a valid design principle outside of Rust too — they're directly useful for reframing concurrency bugs caused by shared mutable state in Go or TypeScript around the question "who owns this data?"
Where it lands in Jayverse
- Rabbit: name the single owner of session-key allowance state. Before implementing concurrent mandate execution under EIP-7702, write down which one component holds write access to a session's remaining allowance at any moment, the same one-mutable-reference rule the borrow checker enforces.
- Bridge/Token: give the relayer a single-owner claim path. For concurrent Anvil/Sepolia event processing, use an index-based claim or a single-owner queue for the mint counter and nonce state, instead of shared mutable references two workers could both touch.
- Auditor: ask "who owns this state right now" for every concurrent off-chain service. Apply it to the relayer and any indexer, not only to Rust code, since aliased mutable state is the same bug in Go or TypeScript.
Key expressions
| Expression | 뜻 · 쓰이는 자리 |
|---|---|
| fall back to | (대안으로) 물러나 ~을 쓰다 · 참조 대신 인덱스 기반 arena를 쓰는 대안. "you fall back to an arena that uses indices instead of references" |
| sidestep | (문제를) 정면돌파 없이 피해가다 · 설계 단계에서 borrow checker와의 싸움을 우회하는 것. "lets you sidestep that fight at the design stage" |
| as a last resort | 최후의 수단으로 · unsafe 코드를 안전한 API 뒤에 감춰 쓰는 경우. "or, as a last resort, unsafe" |
| push X to runtime | X를 런타임으로 미루다(넘기다) · 컴파일 타임 검사를 실행 시점 검사로 바꾸는 것. "push the check to runtime" |
| run into | (문제·오류에) 부딪히다, 맞닥뜨리다 · 일부러 컴파일 실패를 유발해보는 연습. "deliberately run into a compile failure" |
| reframe | (문제를) 다른 틀로 다시 바라보다 · 소유권 개념을 다른 언어의 동시성 버그에 적용할 때. "directly useful for reframing concurrency bugs" |
| NLL | 비어휘적 생애주기(Non-Lexical Lifetimes) · 참조의 유효 범위를 실제 마지막 사용 지점까지로 좁혀주는 러스트 borrow checker 개선. "NLL (Non-Lexical Lifetimes) computes a borrow's valid range" |
| Rc<RefCell<..>> | 참조 카운팅(Rc) + 내부 가변성(RefCell) 조합 · 컴파일타임 검사를 런타임 검사로 미루는 대표적 러스트 패턴. "Rc and RefCell that push the check to runtime" |
| arena | 인덱스 기반 메모리 풀(arena allocator) · 참조 대신 인덱스를 사용해 borrow checker 제약을 우회하는 대안. "an arena that uses indices instead of references" |
If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.