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?"
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/.