Ante: 빌림 검사와 참조 카운팅을 결합하는 새 방식
19 hours ago
3
- Ante는 참조 카운팅의 유연성과 빌림 검사의 안전성을 함께 쓰면서, Rust식 런타임 패닉이나 Swift식 독점 접근 검사 오버헤드를 피하려는 시스템 언어 설계임
- 핵심 장치는 shape-stability와 temporary uniq conversion으로, 참조 카운팅된 값의 필드에는 안전하게 가변 빌림을 만들고 유니언 내부 값은 제한된 범위에서만 uniq로 다룸
- Rust의 Rc<RefCell<T>>는 잘못 사용하면 런타임에 패닉이 날 수 있고, Swift의 borrowing system은 런타임 독점 접근 검사를 포함하지만, Ante는 일부 사례를 컴파일타임 규칙으로 처리하려 함
- 아직 일부만 구현된 work-in-progress 설계이며, 타입을 재귀적으로 분석해 특정 객체에 도달 가능한지를 판단해야 하므로 필드 추가가 breaking API change가 될 수 있음
- 이 접근은 shared mutable borrowing이 항상 불가능하다는 전제를 약화시키며, Vale, group borrowing, Rust GhostCell 같은 기법과 함께 메모리 안전성 설계의 예외 영역을 넓히고 있음
Ante가 목표로 하는 결합
- Ante는 메모리 안전성과 스레드 안전성을 갖춘 더 단순한 Rust를 목표로 하는 시스템 프로그래밍 언어임
- 기본 모델은 단일 소유권과 빌림 검사이며, 값은 스택이나 포함하는 구조체·배열 안에 인라인으로 놓임
- 단순성을 우선하고 싶을 때는 타입에 shared 키워드를 붙여 참조 카운팅을 선택할 수 있음
- shared type Color, shared type RbTree t를 사용한 red-black tree balance 함수는 Python 예시만큼 짧고 C++·Rust 예시보다 작음
- 핵심 관심사는 참조 카운팅된 데이터를 가변으로 빌릴 때 Rust의 borrow_mut() 패닉 위험이나 Swift의 런타임 독점 접근 검사 없이 처리하는 방법임
- Ante는 아직 work-in-progress 상태이며, 일부는 구현됐고 일부는 이론적이며 설계도 변하는 중임
shape-stability와 여러 가변 참조
- Ante의 shape-stability는 “stable shape를 가진 대상에 대한 참조는 다른 곳에서 어떤 변경이 일어나도 항상 유효하다”는 개념임
- 이 개념 덕분에 같은 구조체에 대해 여러 개의 가변 빌림 참조를 동시에 가질 수 있음
- heal (healer: mut Entity) (target: mut Entity) 예시에서는 같은 Entity를 두 인자로 넘겨 자기 자신을 치유하는 self_heal 호출이 가능함
- healer와 target이 같은 Entity를 가리켜도 이 코드에서는 Entity를 파괴할 수 없으므로 두 참조가 계속 유효함
- 구조체 자체와 그 필드, 필드의 필드에 대한 가변 참조도 동시에 허용될 수 있음
- ship: mut Spaceship와 engine_alias: mut Engine = ship.engine를 동시에 사용해도, 함수 실행 중 ship과 그 안의 engine이 파괴되지 않는다고 판단함
- Rust와 Swift에서는 같은 데이터에 여러 &mut 참조가 동시에 가리키는 형태를 허용하지 않음
참조 카운팅된 값의 필드 가변 빌림
- Ante에서 타입 정의 앞에 shared를 붙이면 해당 타입은 자동으로 참조 카운팅됨
- shared mut type Spaceship 예시에서는 launch가 Rc에 해당하는 Spaceship을 보유하면서 mut ship.engine을 set_fuel에 넘김
- launch가 포함 객체인 Spaceship을 유지하므로, 그 필드인 engine도 살아 있다고 판단할 수 있음
- 일반 규칙은 shared mut 타입의 필드에 대해 항상 mut 빌림 참조를 만들 수 있다는 것임
- 단, 그 필드 안쪽에 들어 있는 모든 대상에 대해 항상 가변 빌림을 만들 수 있는 것은 아니며, 별도 규칙이 필요함
- 이후 예시는 설탕 문법인 shared mut type Spaceship 대신 더 명시적인 Rc Spaceship 표기를 사용함
- shared mut type Spaceship은 type Spaceship이 되고, var ship: Spaceship은 var ship: Rc Spaceship이 됨
유니언이 안전성 문제를 만드는 지점
- 유니언은 내용을 인라인으로 보관해 포인터 추적과 캐시 미스를 줄일 수 있어 속도에 유리함
- C의 union Engine이 struct Spaceship 안에 들어가면 StringTheoryEngine과 ImpulseEngine이 Spaceship 메모리 안에 위치함
- Java처럼 인터페이스와 포인터를 사용하는 방식과 대비됨
- 문제는 메모리 안전 언어에서 유니언을 안전하게 지원하기 어렵다는 점임
- Engine이 StringTheoryEngine(str: String) 또는 ImpulseEngine(fuel: I32)인 예시에서는 ship과 other_ship이 같은 Spaceship을 가리킬 때 세그폴트가 날 수 있음
- match uniq ship.engine으로 문자열 내부 참조를 잡은 뒤
- other_ship.engine := ImpulseEngine 0x42로 같은 엔진을 다른 변형으로 바꾸고
- 이어서 기존 str을 수정하면 컨테이너가 파괴된 뒤 내부를 사용하는 문제가 생김
- 따라서 Ante는 가변 빌림 참조가 유니언을 가리킬 때 그 변형 중 하나에 대한 가변 빌림 참조를 만들 수 없도록 해야 함
- 이는 구조체 규칙과 반대임
- 구조체에 대한 mut 참조가 있으면 필드에 대한 mut 참조를 만들 수 있음
- 유니언에 대한 mut 참조가 있으면 변형 내부에 대한 mut 참조를 만들 수 없음
uniq와 temporary uniq conversion
- uniq는 exclusive mutable reference, 즉 독점 가변 참조를 뜻함
- 어떤 변수가 uniq Spaceship을 담고 있다면, 그 Spaceship에 대한 유일하게 사용 가능한 참조임
- Rust의 &mut Spaceship과 비슷한 개념임
- 유니언 내부를 안전하게 다루기 위해 Ante는 temporary uniq conversion을 사용함
- 핵심 규칙은 특정 범위에서 다른 별칭 가능 참조를 사용하지 않는다면 임시로 uniq 참조를 얻을 수 있다는 것임
- match uniq ship.engine 구간에서는 ship.engine에 대해 uniq처럼 접근함
- 이 구간 동안 컴파일러는 Spaceship을 간접적으로 포함할 수 있는 다른 기존 변수를 사용하지 못하게 함
- Rust는 “다른 참조가 어딘가에 있을 수 있다”는 이유로 uniq 존재 자체를 막는 반면, Ante는 해당 범위에서 그 참조들을 사용하지 않는 조건으로 uniq를 허용함
- 이때 uniq Spaceship은 실제로 전역적으로 유일한 참조가 아니라, 해당 범위 안에서 유일하게 사용 가능한 참조임
- C의 restrict 포인터와 유사한 뉘앙스를 가짐
허용되는 접근과 거부되는 접근
- match uniq ship.engine 범위 안에서 other_ship: Rc Spaceship에 접근하면 컴파일 오류가 나야 함
- other_ship.engine이 ship.engine과 alias일 수 있고
- ship.engine을 사용하는 동안 other_ship.engine 변경이 drop을 유발할 수 있기 때문임
- HasAShip처럼 Rc Spaceship을 필드로 가진 다른 구조체도 같은 이유로 거부됨
- other.ship.engine도 간접적으로 같은 Spaceship에 도달할 수 있음
- 반대로 new_fuel: I32 같은 정수는 사용할 수 있음
- I32는 Spaceship에 대한 참조를 포함할 수 없기 때문임
- Spaceship 자체가 follow_ship: Rc Spaceship 같은 필드를 포함하면 거부됨
- 그 경우 uniq Spaceship도 자기 내부 경로를 통해 다시 도달 가능해지므로, 일반적으로 재귀 타입에는 mut -> uniq 변환을 할 수 없음
함수 호출과 반환에서의 제약
- 함수 호출에서도 mut -> uniq 변환이 일어날 수 있음
- foo (var ship: Rc Spaceship) (new_res: Resonator)가 maybe_use_resonator ship new_res를 호출할 때, 호출 지점에서 ship이 uniq Spaceship으로 변환됨
- 컴파일러는 다른 인자가 Spaceship 참조를 포함할 수 있는지만 확인하면 됨
- 예시의 Resonator는 그런 참조를 포함하지 않으므로 허용됨
- 반환에서는 변환된 uniq 참조를 일반 uniq로 돌려줄 수 없음
- 반환 후에는 “범위 안에서 alias 가능 변수를 사용하지 않는다”는 컴파일러 검사가 적용되지 않기 때문임
- 대신 반환 타입을 local uniq Foo로 지정할 수 있음
- 내부적으로 mut ref에서 uniq ref로 변환할 때 실제로는 항상 local uniq가 만들어짐
- 대부분의 경우 일반 uniq처럼 사용할 수 있지만, 반환할 때는 명시가 필요함
설계상의 비용과 대안
- Ante는 Rc Spaceship 같은 참조 카운팅 참조를 런타임 오류 없이 임시 uniq Spaceship으로 바꿀 수 있음
- 단점은 컴파일러가 “Engine에서 Spaceship에 도달할 수 있는가” 같은 질문에 답하기 위해 타입을 재귀적으로 살펴봐야 한다는 점임
- 이런 분석은 취약할 수 있음
- 구조체에 필드를 추가하는 일이 breaking API change가 될 수 있음
- Ante의 제작자인 Jake는 이 보장을 유지하는 더 나은 방법을 찾고 있음
- group borrowing과 Flix references처럼 각 공유 가변 타입에 익명 고유 브랜드 타입을 붙이는 방식
- 공유 타입을 변경할 때 Mutates 'a 같은 effect를 추가해 타입 분석을 제거하는 방식
- 두 참조가 다른 객체를 가리키는지 사용자가 런타임에 검사하거나, safe API로 감싼 unsafe 검사를 제공하는 방식
- 컴파일러가 Rc 내부에 간접 저장되지 않아 alias될 수 없는 값을 추적하는 방식
- Pony의 iso permission과 비슷한 아이디어, 또는 구조체 내부를 보되 바깥을 가리키는 참조는 사용하지 못하게 하는 임시 권한도 가능성으로 남아 있음
- 어려운 부분은 이런 유연성을 유지하면서 Ante의 목표인 사용성, 가독성, 단순성을 지키는 것임
더 넓은 메모리 안전성 흐름
- shared mutable borrowing은 예전에는 불가능하다고 여겨졌고, Rust도 그런 믿음 위에 설계됐다는 관점을 깔고 있음
- 여러 예외가 누적되고 있음
- Ante는 local uniqueness 규칙을 통해 shared-mutable 데이터에서 uniq 빌림 참조를 얻을 수 있음
- Vale은 pure function을 통해 shared-mutable 데이터에서 불변 빌림 참조를 얻을 수 있음
- group borrowing은 shape-stable이 아니어도 shared-mutable 빌림 참조를 만들 수 있음
- Rust의 GhostCell은 객체 그래프가 서로 자유롭게 가리킬 수 있게 하지만, 특정 시점에는 그중 하나에 대한 가변 참조 하나만 가질 수 있음
- 이 흐름은 메모리 안전성 설계에서 shared mutable borrowing을 다루는 더 일반적인 원리가 있을 가능성을 암시함
Rust Cell과의 비교
- Rust 사용자는 구조체 필드에 Cell을 넣는 방식과 Ante 접근의 차이를 물을 수 있음
- Ante 예시에서는 Rc Spaceship에서 status: String에 대한 mut String 참조를 얻어 " (refueling)"을 직접 붙일 수 있음
- Rust의 Cell<String> 방식에서는 Rc<Spaceship>에서 &mut String을 얻을 수 없음
- 대신 status_ref.replace(String::new())로 임시 기본값을 넣고
- 꺼낸 String을 수정한 뒤
- 마지막에 다시 replace(status)로 되돌려야 함
- 이 방식에는 몇 가지 단점이 있음
- "" 같은 기본 인스턴스를 만들어야 함
- 마지막 replace 호출을 잊을 위험이 있음
- 값이 교체된 상태에서 누군가 status를 읽을 위험이 있음
- Ante는 임시로 status 문자열에 대한 참조를 얻도록 하고, 그동안 다른 코드가 접근하지 못하게 컴파일러가 강제함
-
Homepage
-
Tech blog
- Ante: 빌림 검사와 참조 카운팅을 결합하는 새 방식