TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 22 READS

Rust 빌림 검사기를 뒤집어 보기 — '언제까지 사는가' 대신 '어디서 빌려왔는가'를 묻는 Polonius 이야기

Rust를 공부하다 보면 누구나 한 번쯤 빌림 검사기(borrow checker) 앞에서 멈춰 서게 되는데요. 분명 내 눈에는 아무 문제 없는 코드인데 컴파일러가 '이 참조는 여기서 쓸 수 없습니다' 하고 막아서는 경험, Rust 입문자라면 다들 해보셨을 거예요. 오늘 소개할 글은 Rust 언어 설계의 핵심 인물인 니코 마차키스(Niko Matsakis)가 쓴 글인데, 빌림 검사기를 완전히 다른 관점에서 다시 정의하는 내용이거든요. 2018년에 쓰인 글이지만, 이후 Rust 컴파일러의 방향에 큰 영향을 준 'Polonius'라는 프로젝트의 출발점이 된 글이라 지금 읽어도 배울 게 정말 많아요.

빌림 검사기, 뭐가 문제였길래

먼저 빌림 검사기가 뭐냐면요. Rust는 메모리를 자동으로 치워주는 가비지 컬렉터 없이도 메모리 안전성을 보장하는 언어인데, 그 비결이 컴파일 시점에 '누가 이 데이터를 빌려 쓰고 있는지'를 전부 추적하는 거예요. 규칙 자체는 단순해요. 데이터를 고쳐 쓸 수 있는 가변 참조는 한 번에 딱 하나만, 읽기만 하는 불변 참조는 여러 개까지 허용. 이 규칙만 지키면 다른 언어에서 흔한 '이미 해제된 메모리를 건드리는 버그' 같은 게 원천 차단되거든요.

문제는 이 규칙을 검사하는 방식이었어요. 전통적인 방식은 '수명(lifetime)'이라는 개념을 쓰는데, 각 참조가 코드의 어느 구간까지 살아있는지를 계산해서 구간이 겹치면 에러를 내는 식이에요. 처음엔 이 구간을 중괄호 블록 단위로 계산했는데, 너무 보수적이라 멀쩡한 코드도 자주 거부됐어요. 그래서 나온 개선이 NLL(Non-Lexical Lifetimes)인데요, 이게 뭐냐면 참조가 마지막으로 실제 사용되는 지점까지만 살아있다고 보는 더 정밀한 계산법이에요. 블록이 끝날 때까지가 아니라요.

그런데 NLL로도 해결 안 되는 유명한 케이스가 남았어요. 해시맵에서 값을 찾아보고, 없으면 새로 넣는 아주 흔한 패턴이거든요. 사람 눈에는 '없으면'이라는 분기로 들어온 시점에 앞서 빌린 참조는 이미 쓸모가 없어진 게 명백한데, 컴파일러는 함수 시그니처상 그 참조가 아직 살아있을 수 있다고 판단해서 삽입을 막아버려요. 안전한 코드인데 우회 코드를 짜야 하는 상황이 생기는 거죠.

발상의 전환: 방향을 거꾸로 뒤집기

이 글에서 마차키스가 제안하는 건 질문 자체를 바꾸는 거예요. 기존 방식이 '이 참조가 미래의 언제까지 살아있는가'를 물었다면, 새 방식은 '이 참조가 과거의 어디에서 빌려온 값인가'를 물어요. 시간의 방향을 거꾸로 뒤집은 셈인데요. 수명을 코드 구간이 아니라 '이 참조가 가리킬 가능성이 있는 대출(loan)들의 집합'으로 다시 정의하는 거예요. 그래서 이 접근을 별칭(alias) 기반 정식화라고 불러요. 어떤 참조가 어떤 원본 데이터의 별칭이 될 수 있는지를 추적한다는 뜻이거든요.

이렇게 관점을 바꾸면 아까 그 해시맵 문제가 자연스럽게 풀려요. '없으면' 분기에서는 앞서 만든 대출이 더 이상 유효할 필요가 없다는 걸 정확히 추론할 수 있게 되니까요. 더 흥미로운 건 구현 방식이에요. 이 분석 규칙 전체를 Datalog라는 언어로 기술했는데, Datalog가 뭐냐면 '사실'과 '추론 규칙'만 선언하면 엔진이 알아서 결론을 도출해주는 논리형 프로그래밍 언어예요. 복잡한 분석 알고리즘을 손으로 짜는 대신, '대출 L이 지점 P에서 유효하려면 이런 조건이 성립해야 한다' 같은 규칙 몇 줄로 검사기의 핵심을 표현할 수 있게 된 거죠. 컴파일러의 정확성을 수학적으로 따져보기도 훨씬 쉬워지고요.

이 글이 남긴 것들

NLL은 Rust 2018 에디션에 정식 탑재됐고, 이 글에서 출발한 Polonius는 그 다음 세대 빌림 검사기 연구의 이론적 토대가 됐어요. Rust 팀은 이후로도 이 정식화를 다듬어서 차세대 빌림 검사기 작업에 반영해 왔거든요. 시야를 넓혀 보면, 소유권과 빌림이라는 아이디어 자체가 업계 전반으로 퍼지고 있어요. Swift의 배타적 접근 검사나 최근 등장하는 시스템 언어들이 Rust의 모델을 참고하고 있고, 그 뿌리를 거슬러 올라가면 Cyclone 같은 연구용 언어까지 닿아요. 그러니 이 글은 한 언어의 내부 구현 이야기이면서, 동시에 '컴파일러가 메모리 안전을 증명하는 방법'이라는 더 큰 흐름의 중요한 장면이기도 한 거예요.

한국 개발자에게는

Rust를 실무에서 쓰거나 공부 중이라면, 이 글의 멘탈 모델이 당장 도움이 돼요. 빌림 에러를 만났을 때 '이 참조가 언제 죽는 거지?'라고 고민하는 대신 '이 참조가 어디서 빌려온 값이고, 그 원본이 지금 어떤 상태지?'라고 질문을 바꿔보면 에러 메시지가 훨씬 잘 읽히거든요. 컴파일러 이론이나 정적 분석에 관심 있는 분에게는 Datalog로 프로그램 분석을 기술하는 사례 자체가 좋은 교재고요. 당장 Rust를 안 쓰더라도, 이런 분석이 어떻게 동작하는지 알아두면 언젠가 다른 언어에서 비슷한 개념을 만났을 때 낯설지 않을 거예요.

정리하면, 이 글은 빌림 검사기라는 난공불락의 벽을 '질문을 뒤집는 것'만으로 훨씬 유연하게 만든 사례예요. 여러분은 Rust의 빌림 검사기를 어떻게 이해하고 계신가요? 벽에 부딪혔다가 나만의 방식으로 뚫어낸 경험이 있다면 공유해 주세요.


🔗 출처: Hacker News

SOURCE · HACKER NEWS
원문 전체 보기 → https://smallcultfollowing.com/babysteps/blog/2018/04/27/an-...
SHARE
처리 중...