# Ante: 빌림 검사와 참조 카운팅을 결합하는 새 방식

> Clean Markdown view of GeekNews topic #30954. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=30954](https://news.hada.io/topic?id=30954)
- GeekNews Markdown: [https://news.hada.io/topic/30954.md](https://news.hada.io/topic/30954.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-06-30T09:08:29+09:00
- Updated: 2026-06-30T09:08:29+09:00
- Original source: [verdagon.dev](https://verdagon.dev/blog/ante-blending-borrowing-rc)
- Points: 1
- Comments: 1

## Topic Body

- Ante는 **참조 카운팅**의 유연성과 **빌림 검사**의 안전성을 함께 쓰면서, Rust식 런타임 패닉이나 Swift식 독점 접근 검사 오버헤드를 피하려는 시스템 언어 설계임
- 핵심 장치는 **shape-stability**와 temporary uniq conversion으로, 참조 카운팅된 값의 필드에는 안전하게 가변 빌림을 만들고 유니언 내부 값은 제한된 범위에서만 `uniq`로 다룸
- Rust의 `Rc<RefCell&lt;T&gt;>`는 잘못 사용하면 런타임에 패닉이 날 수 있고, 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** 상태이며, 일부는 구현됐고 일부는 이론적이며 설계도 변하는 중임
  - 진행 상황은 [Ante 사이트](https://antelang.org/)와 [Discord](https://discord.gg/BN97fKnEH2)에서 확인할 수 있음

### 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](https://verdagon.dev/blog/group-borrowing)과 [Flix references](https://doc.flix.dev/references.html)처럼 각 공유 가변 타입에 익명 고유 브랜드 타입을 붙이는 방식
  - 공유 타입을 변경할 때 `Mutates 'a` 같은 effect를 추가해 타입 분석을 제거하는 방식
  - 두 참조가 다른 객체를 가리키는지 사용자가 런타임에 검사하거나, safe API로 감싼 unsafe 검사를 제공하는 방식
  - 컴파일러가 `Rc` 내부에 간접 저장되지 않아 alias될 수 없는 값을 추적하는 방식
- Pony의 [iso permission](https://tutorial.ponylang.io/reference-capabilities/reference-capabilities#the-list-of-reference-capabilities)과 비슷한 아이디어, 또는 구조체 내부를 보되 바깥을 가리키는 참조는 사용하지 못하게 하는 임시 권한도 가능성으로 남아 있음
- 어려운 부분은 이런 유연성을 유지하면서 Ante의 목표인 **사용성**, **가독성**, **단순성**을 지키는 것임

### 더 넓은 메모리 안전성 흐름
- shared mutable borrowing은 예전에는 불가능하다고 여겨졌고, Rust도 그런 믿음 위에 설계됐다는 관점을 깔고 있음
- 여러 예외가 누적되고 있음
  - Ante는 local uniqueness 규칙을 통해 shared-mutable 데이터에서 `uniq` 빌림 참조를 얻을 수 있음
  - [Vale](https://vale.dev/)은 pure function을 통해 shared-mutable 데이터에서 불변 빌림 참조를 얻을 수 있음
  - [group borrowing](https://verdagon.dev/blog/group-borrowing)은 shape-stable이 아니어도 shared-mutable 빌림 참조를 만들 수 있음
  - Rust의 [GhostCell](https://docs.rs/ghost-cell/latest/ghost_cell/)은 객체 그래프가 서로 자유롭게 가리킬 수 있게 하지만, 특정 시점에는 그중 하나에 대한 가변 참조 하나만 가질 수 있음
- 이 흐름은 메모리 안전성 설계에서 shared mutable borrowing을 다루는 더 일반적인 원리가 있을 가능성을 암시함

### Rust `Cell`과의 비교
- Rust 사용자는 구조체 필드에 `Cell`을 넣는 방식과 Ante 접근의 차이를 물을 수 있음
- Ante 예시에서는 `Rc Spaceship`에서 `status: String`에 대한 `mut String` 참조를 얻어 `" (refueling)"`을 직접 붙일 수 있음
- Rust의 `Cell&lt;String&gt;` 방식에서는 `Rc&lt;Spaceship&gt;`에서 `&mut String`을 얻을 수 없음
  - 대신 `status_ref.replace(String::new())`로 임시 기본값을 넣고
  - 꺼낸 `String`을 수정한 뒤
  - 마지막에 다시 `replace(status)`로 되돌려야 함
- 이 방식에는 몇 가지 단점이 있음
  - `""` 같은 **기본 인스턴스**를 만들어야 함
  - 마지막 `replace` 호출을 잊을 위험이 있음
  - 값이 교체된 상태에서 누군가 `status`를 읽을 위험이 있음
- Ante는 임시로 `status` 문자열에 대한 참조를 얻도록 하고, 그동안 다른 코드가 접근하지 못하게 컴파일러가 강제함

## Comments



### Comment 60774

- Author: neo
- Created: 2026-06-30T09:08:31+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/vv4fhi/ante_new_way_blend_borrow_checking) 
- “**공유된 가변 차용**”이 불가능하다고 여겼던 것은 단순히 Rust가 목표를 달성하려고 감수한 희생이 아니라, Rust의 핵심 목표 자체에 가까움  
  공유 가변 상태는 코드에 대한 **국소적 추론**을 어렵게 만들기 때문임  
  ["References are like jumps" by withoutboats](https://without.boats/blog/references-are-like-jumps/)가 이 점을 잘 다룸. 별칭이 있는 상태를 우연히 변경하지 못하게 막는 것이 올바르게 동작하는 시스템을 쉽게 만들기 위한 핵심이고, Rust의 수명 규칙은 단순히 가비지 컬렉션을 피하려는 장치가 아니라 가변 상태와 별칭 상태를 함께 허용하는 언어에서 추론 가능성을 확보하는 더 깊은 구조라는 주장임
  - 예전에 저자가 **Mojo borrow checker**를 다뤘을 때도 같은 생각이 들었음. Rust의 차용 검사기는 단일 스레드 프로그램에서도 **값 의미론**을 유지해 줌

- 꽤 괜찮아 보임  
  이해한 게 맞다면, 공유 참조에서 가변 참조로 넘어가는 마법은 스레드 간에 공유되지 않는 타입에 한정되기 때문에 가능하고, `Rc`의 유일성은 같은 타입의 모든 객체가 같은 수명으로 차용된 것처럼 취급해서 보장하는 듯함  
  명시적 문법과 자연스러운 문법 중 무엇이 좋은지는 취향 문제일 수 있지만, 컴파일러가 `Cell`에 대해 더 많이 알면 그에 대한 가변 참조를 더 유연하게 허용할 수 있음을 보여줌  
  그리고 Rust에서 `mut`가 가변이 아니라 **배타적/유일함**을 뜻하는 것처럼 쓰이는 혼란스러운 용어도 피함
  - 스레드 간에서는 어떻게 되는지 궁금했음. “`uniq` 승격이 잠금 획득을 의미하나?” 같은 의문이었는데, 비교 대상이 `Arc`가 아니라 **`Rc`** 라는 뜻으로 이해됨
  - `mut`가 배타적/유일함을 뜻한다는 부분을 좀 더 설명해 줄 수 있음?

- 끝부분에서 암시한 **통합 원리**가 무엇일지 짐작 가는 사람이 있는지 궁금함

- [antelang.org 블로그 글에 대한 이전 논의들](https://lobste.rs/domains/antelang.org)도 참고할 만함

- 이게 어떻게 동작하는지 잘 모르겠음. “객체에 대한 가변 포인터가 있으면 그 객체의 슬라이스에 대한 변경 참조를 얻을 수 있다”는 뜻처럼 보임  
  그런데 그렇다면 예를 들어 `mutref someobjext = …`, `mutref subfield = someobjext.a.b`, `someobjext.a = somethingelse` 같은 식이 가능해 보이고, 그러면 `subfield`가 무효가 되거나 값이 바뀌면서 깨질 수 있음  
  글에는 설명, 다른 언어와의 비교, 코드 예시는 많았지만, 정작 이 동작의 **단계별 의미론**을 기본적으로 정리한 부분을 찾기 어려웠음
