Lobsters

Beyond the &

참조 연산자 & 너머

Rust 언어팀이 사용자 정의 스마트 포인터도 내장 참조처럼 다룰 수 있도록 컴파일러의 ‘place’ 개념을 라이브러리 코드에 노출하는 설계를 제안했습니다. 아직 프로토타입 단계이며, borrow checker가 포인터별 읽기·쓰기·빌리기 규칙을 이해하도록 만드는 것이 목표입니다.

AI 요약

Rust의 내장 참조에서는 가능한 작업이 사용자 정의 스마트 포인터에서는 막히는 경우가 있습니다. Rust 언어팀을 이끄는 Tyler Mandry는 RustConf 2026에서 이 차이를 줄이려는 장기 작업 ‘Beyond the &’를 소개했습니다. 목표는 스마트 포인터도 내장 참조처럼 borrow checker와 자연스럽게 상호작용하도록 만드는 것입니다.

MutexGuard를 쓰면 달라지는 필드 대여

기사의 예제는 템플릿으로 동적 콘텐츠를 만들고, 결과를 HashMap에 캐시하는 코드입니다. HashMap의 Entry API에서 or_insert_with()를 호출하는 동안 캐시 항목을 빌리고, 콜백에서는 별도 필드인 template을 읽습니다. RenderState를 &mut으로 받는 원래 코드에서는 borrow checker가 두 필드에 대한 접근이 서로 겹치지 않는다는 점을 추적하므로 동작합니다.

하지만 RenderState를 Mutex로 감싸고 lock()의 결과인 MutexGuard를 사용하면 컴파일이 거부됩니다. borrow checker가 MutexGuard를 통한 접근을 불투명하게 보아 cache와 template이 서로 다른 필드라는 사실을 알아내지 못하기 때문입니다. 콜백에서 template을 읽는 동안 cache를 변경할 가능성을 배제하지 못합니다. MutexGuard를 한 번 역참조해 내부 구조체를 다시 대여하면 해결되지만, Mandry는 이런 차이가 Rust의 학습을 어렵게 만든다고 설명했습니다.

포인터가 가리키는 장소를 다루는 설계

언어팀이 검토한 해법은 컴파일러 내부 개념인 place를 사용자 코드에 노출하는 것입니다. place는 값을 읽거나 쓸 수 있는 위치를 나타내는 컴파일 시점의 추상 표현입니다. C의 lvalue와 비슷하며, state.cache 같은 표현이 여기에 해당합니다. 포인터는 런타임에 place를 가리키는 한 가지 방식입니다.

설계안에서는 각 스마트 포인터에 대응하는 handle 타입을 둡니다. 보통 handle은 같은 위치를 가리키는 unsafe 포인터를 감쌉니다. 컴파일러는 프로그램에서 참조하는 place를 나타내는 handle을 자동으로 만들고, 라이브러리는 handle 타입에 trait을 구현해 borrow checker가 해당 스마트 포인터를 다루는 방식을 정합니다. 기존 Deref와 DerefMut보다 세밀하게 규칙을 지정할 수 있습니다.

예를 들어 WritePlace는 handle이 가리키는 place에 값을 쓰는 방법을 정의합니다. NonNullHandle에 구현할 때 SAFE를 false로 설정하면 해당 place에 쓰는 작업을 unsafe로 취급합니다. 잘못된 구현은 borrow checker의 판단을 흐려 메모리 안전성을 깨뜨릴 수 있으므로 trait 구현 자체도 unsafe입니다. ReadPlace는 읽는 방식을 정의합니다.

ProjectPlace는 구조체를 담은 place에서 특정 필드의 place를 만드는 규칙을 표현합니다. 이를테면 MutexGuard<RenderState>에 대응하는 handle에서 state.template에 해당하는 필드로 이동하는 방법을 정합니다. BorrowPlace는 & 연산자처럼 주어진 place를 빌려 새 스마트 포인터를 만드는 규칙을 맡습니다. 여기에는 접근이 공유인지 배타적인지, 대여 기간이 얼마인지, 생성이 안전한지 같은 정보가 포함됩니다.

참조 규칙을 같은 체계로 표현

새 스마트 포인터를 만드는 문법은 아직 논의 중입니다. 기존 &를 재사용하면 혼란을 주거나 타입 추론을 어렵게 만들 수 있습니다. @ 기호를 쓰자는 제안도 나왔지만 합의되지는 않았습니다. 문법이 어떻게 정해지든 BorrowPlace가 borrow checker에 필요한 안전성 정보를 제공한다는 구상입니다.

내장 참조도 같은 체계로 정의할 수 있습니다. &T에 해당하는 구현은 여러 공유 참조를 허용하고, 대여 기간을 'a로 지정하며, 참조 생성이 안전하다고 표시합니다. 이 방식은 라이브러리 작성자가 참조처럼 작동하는 스마트 포인터를 만드는 예가 됩니다. 표준 라이브러리 관리자는 기존 참조의 직관적이지 않은 동작도 같은 자리에서 설명할 수 있습니다. MutexGuard에 맞는 handle과 trait을 구현하면 앞서 컴파일에 실패한 render_page() 예제도 동작합니다. Mandry는 예제의 코드 변경은 작지만 의미론적 변화는 크다고 표현했습니다.

패턴 매칭과 남은 설계 과제

place와 handle을 컴파일러 내부 개념에 연결하면 스마트 포인터 뒤에 있는 값의 패턴 매칭도 가능해질 수 있습니다. 현재는 참조 뒤의 값에는 패턴 매칭을 적용할 수 있지만, 스마트 포인터 뒤의 값에는 먼저 포인터를 역참조하고 다시 빌리지 않으면 적용하기 어렵습니다. BorrowPlace가 제공하는 정보를 활용하면 컴파일러가 이를 안전하게 처리할 수 있다는 설명입니다.

이 설계는 아직 프로토타입이며 안정화까지 할 일이 남아 있습니다. 언어팀은 다양한 의미를 지닌 스마트 포인터를 사례로 추가해 설계 문서에 반영해 달라고 요청했습니다. 다음으로 다듬을 부분은 오류와 진단 메시지입니다. 사용자가 새 trait 이름을 오류에서 보지 않도록 내부 구현을 감추고, 내장 참조를 쓸 때처럼 문제를 직접 설명하는 메시지를 목표로 합니다.

질의응답에서 Mandry는 값의 소유권을 가져오면서 패턴을 검사하고 구성 요소를 분해하는 destructive pattern matching도 지원할 수 있다고 답했습니다. 해당 handle에 VariantPlace를 구현해야 합니다. handle의 모든 작업을 지원하려면 여섯 개에서 여덟 개 정도의 연산을 구현해야 하며, VariantPlace도 그중 하나입니다. 열거형의 필드 투영은 일반적으로 지원하기 어려울 수 있지만 탐색 중인 아이디어가 있다고 덧붙였습니다. Option 내부 필드를 투영해 해당 필드의 Option을 얻는 방식이 적절한지에 대해서는 아직 답이 없다고 했습니다.

Lobsters 반응

  • @wrs — 이 제안에서는 trait 구현에 ‘기능 플래그’를 넣어 컴파일러에 의미를 알려주는 것처럼 보입니다. “borrow checker에 같은 place에 여러 참조를 만들 수 있다고 알립니다”라며 AccessKind::Shared를 지정하는 방식은 조금 이상합니다. 의미를 전달할 때는 보통 marker trait을 쓰지 않나요? Rust에 이런 방식이 더 있는데 제가 몰랐던 건가요, 아니면 새로운 기법인가요?
    • @gignico — 네, 새로운 방식인 것 같습니다. 여러 가능한 값 가운데 하나를 선택해야 한다는 점이 이유인 것 같습니다. marker trait은 참과 거짓을 표현합니다. 타입 시스템에는 여러 trait 가운데 정확히 하나만 구현하도록 강제하는 기능이 없습니다. 반면 enum 타입의 연관 상수는 그중 정확히 하나를 지정합니다.
    • @wrs — 네, 그 부분은 이해했습니다. 다만 이 값은 marker trait과 달리 타입 시스템에 실제로 참여하지 않으니, 내장 속성으로 표현할 줄 알았습니다. 예를 들면 #[access_kind(Shared)]를 unsafe impl에 붙이는 식입니다.

원문: LWN.net / 번역·요약: Trawling