Lobsters

Valen's Memory Safety: A New Kind of Borrow Checking

Valen의 메모리 안전성 — 새로운 방식의 대여 검사

Valen은 참조가 가리키는 데이터의 경로를 컴파일러가 추적해 해제 뒤 사용(use-after-free)을 막는 대여 검사 방식을 제안합니다. Rust의 ‘공유 또는 변경’ 규칙 대신 무효화 여부를 검사해 같은 객체를 가리키는 여러 참조의 읽기와 쓰기를 허용하며, Rust 상호운용과 참조 카운팅 지원도 내다봅니다.

AI 요약

Valen의 대여 검사기는 Rust처럼 공유 참조와 변경 참조를 하나씩만 허용하는 대신, 참조가 가리키는 데이터의 위치를 경로(path)로 추적합니다. 글의 저자는 Nick Smith의 Group Borrowing 제안을 바탕으로, 여러 참조가 같은 객체를 읽고 쓰더라도 해제된 데이터에 접근하지 못하게 막는 방식을 설명합니다. 목표는 컴파일 시점에 메모리 안전성을 검사하면서 기존 대여 검사기보다 다양한 코드 패턴을 허용하는 것입니다.

참조가 가리키는 경로 추적

Valen에서는 단일 소유권(single ownership)을 바탕으로 각 객체가 하나의 경로를 가집니다. 예를 들어 world.entities[0]을 가리키는 참조에는 해당 경로가 기록됩니다. 함수 매개변수도 entity in world.entities[] mut처럼 참조가 가리키는 위치와 변경 범위를 표현합니다. 컴파일러는 이 정보를 이용해 어떤 연산이 참조를 무효화하는지 판단합니다.

Vec에 원소를 추가하면 벡터 자체를 가리키는 참조는 유효하지만, 벡터 안의 원소를 가리키는 참조는 무효화될 수 있습니다. 따라서 벡터 변경 뒤 원소 참조를 읽으면 컴파일 오류가 납니다. 반면 변경 대상이 구조체의 크기처럼 타입이 안정적인(type-stable) 필드라면, 다른 필드나 그 안의 고정 크기 데이터 참조까지 모두 무효화하지는 않습니다. 열거형 내부나 포인터가 가리키는 값처럼 위치가 달라질 수 있는 데이터는 무효화 대상입니다.

공유와 변경을 함께 허용하는 방식

Valen은 참조 타입에 변경 가능 여부를 담지 않습니다. 대신 함수 시그니처의 mut 효과로 함수가 어느 경로를 변경하는지 알립니다. 이 설계에서는 동일한 객체를 가리키는 참조가 여럿 있어도, 변경 때문에 실제로 참조가 무효화되지 않는다면 각각을 통해 읽고 쓸 수 있습니다. 예를 들어 두 참조가 같은 목록에 push를 호출하는 코드를 허용할 수 있습니다.

글은 World와 그 안의 Entity를 함께 다루는 예를 듭니다. Rust에서는 엔티티를 변경하려면 &mut World에서 엔티티의 변경 참조를 얻고, 월드에 접근할 때 다시 참조를 얻는 작업을 반복해야 합니다. Valen은 월드 전체는 읽기 전용으로 두고 world.entities[] 안의 엔티티만 변경한다고 시그니처에 표시합니다. 이 방식은 함수가 바꿀 수 있는 범위를 좁혀 API 계약을 분명하게 하고, 호출자도 어떤 데이터가 그대로 유지되는지 알 수 있게 합니다.

Rust 상호운용과 남은 과제

Valen은 Rust 함수를 호출할 때 Group Borrowing 참조를 Rust 참조로 잠시 변환하는 방안을 설명합니다. Rust 함수가 반환하는 참조의 정확한 경로를 Valen이 알 수 없을 때는 in self.gem... 같은 와일드카드 하위 경로(wildcard descendant path)로 반환값이 특정 필드 내부를 가리킨다고 표현합니다. 저자는 이를 라이브러리 내부 구조를 감추는 서명에도 활용할 가능성을 언급합니다.

글은 이 방식이 모든 메모리 안전성 문제를 해결했다고 주장하지는 않습니다. 참조 카운팅과 세대 번호 참조(generational references)를 대여 검사에 결합하는 문제는 후속 과제로 남겨 둡니다. 또 동시성에서 무효화 규칙이 어떻게 작동할지 논의하며, 구조화된 동시성(structured concurrency) 안에서 공유 중인 데이터를 변경하지 못하게 하는 등 일반 대여 검사와 비슷한 규칙을 적용할 수 있다고 설명합니다. 컴파일러 구현과 LLVM 최적화 활용도 앞으로 다룰 과제입니다.

Lobsters 반응

  • @polywolf — 예전에 Group Borrowing 글을 읽었을 때는 공개 필드가 있는 단순한 구조체와 배열에는 맞지만, 이진 트리나 연결 리스트처럼 경로를 추상화해야 하는 경우에는 다루기 어려워질 거라고 생각했습니다. 와일드카드 경로가 이 문제를 해결하네요. 다만 동시성에서 이 시스템이 값 무효화와 TOCTTOU를 어떻게 다룰지는 아직 걱정됩니다. 공유-배타 규칙은 그 점에서 더 유리해 보이지만, 단일 스레드 환경에서는 꽤 괜찮아 보입니다.
    • @Verdagon — 감사합니다. 경로 추상화에는 관련 그룹/경로(associated groups/paths)도 도움이 됩니다. 관련 타입(associated types)의 그룹/경로 버전과 비슷합니다. 구현을 진행할수록 무효화와 일반 대여 검사가 사실상 비슷하다는 점을 알게 됩니다. 둘의 차이는 다른 곳에 있습니다. 동시성도 일반 대여 검사와 같은 방식으로 처리할 수 있다고 생각합니다. 구조화된 동시성으로 데이터를 다른 스레드와 공유하는 동안 그 데이터 안을 가리키는 참조가 있으면 변경할 수 없습니다. 데이터를 다른 스레드로 옮겼다면 이미 이동했으므로 참조를 역참조할 수도 없습니다. 아직 영어로 잘 설명하기 어렵지만, 함수나 범위에 변경 효과가 없다면 사실상 불변 참조를 받는다는 점이 실마리입니다. 그러면 공유-배타 규칙의 일반적인 이점이 적용됩니다.
    • @polywolf — 구조화된 동시성으로 데이터를 다른 스레드와 공유하는 동안 그 데이터 안을 가리키는 참조가 있으면 변경할 수 없다는 말은 제가 말한 것과 비슷하게 들립니다. 공유-배타 규칙과 아주 닮았네요. 한 가지 방안은 클로저 안에 변경 효과가 있는지 표시하는 클로저 타입을 정의하고, 구조화된 동시성은 변경 효과가 없는 클로저만 받게 하는 것입니다. 뮤텍스는 변경 효과가 있는 클로저를 실행하되 바깥에는 변경 효과를 드러내지 않게 만들고요. 맞나요?
    • @Verdagon — 네, 정확히 그렇습니다. 잘 정리하셨습니다.
  • @davidbalbert — 안전한 공유 변경은 새 언어에서 제가 바라는 기능 중 하나입니다. 이 방식이 OxCaml의 Modes와 어떤 관계인지 감이 있으신가요? Ante의 안전한 공유 변경은 OxCaml의 uniqueness mode와 본질적으로 같다는 느낌이 들지만 확신은 없습니다. Group Borrowing이 Ante 방식과 다르다는 점은 알지만 서로 관련이 있어 보입니다. OxCaml에 대한 기대는 여전히 있지만 예전보다는 줄었습니다. Modes는 우아하지만 언어에 모드가 계속 늘고 있습니다. 가능한 조합이 너무 많아져 언어가 다루기 어려워질까 걱정됩니다. 문서의 “덮어쓸 수 있는 메모리 위치를 포함하지 않는 타입에는 고유성이 관련 없다”는 설명이 떠오릅니다. Ante에서 말하는 shape-stable과 같아 보이며, 글에서 부르는 type-stable과도 같은 개념 같습니다.

원문: Verdagon / 번역·요약: Trawling