TECH 으로 돌아가기
TECH HACKER NEWS 오늘 9분 읽기 24 READS

러스트의 스마트 포인터, 내장 참조처럼 다루는 'Beyond the &'

러스트의 스마트 포인터, 내장 참조처럼 다루는 'Beyond the &'
SOURCE IMAGE · HACKER NEWS

러스트의 강력함이자 진입 장벽으로 꼽히는 요소 중 하나가 참조(reference)와 빌림 검사기(borrow checker)다. 표준 라이브러리와 사용자 코드에는 다양한 스마트 포인터가 존재하지만, 내장 참조로는 되는 일이 사용자 정의 스마트 포인터로는 안 되는 경우가 적지 않다. 러스트 프로젝트 언어 팀을 이끄는 타일러 맨드리(Tyler Mandry)는 RustConf 2026에서 이 간극을 메우려는 'Beyond the &' 작업을 소개했다. 그는 2026년 내내 언어 팀의 여러 구성원과 함께 이 문제를 붙들어 왔고, 포인터와 참조의 동작을 지금보다 단순한 머릿속 모델로 설명할 수 있는 설계에 도달했다고 밝혔다.

뮤텍스가 부른 빌림 검사기의 거절

문제를 보여주기 위해 맨드리는 동적 콘텐츠를 계산해 해시 맵에 캐싱하는 간단한 예제를 들었다. 맵 엔트리 API의 or_insert_with()로 캐시 키를 조회하고, 값이 없으면 템플릿에 이름을 대입해 계산하는 코드다. 공유 상태는 &mut RenderState라는 내장 가변 참조로 접근한다. 이 코드는 잘 동작하지만 스레드 간에 안전하게 공유할 수 없다. 그래서 캐시를 공유하려고 뮤텍스를 추가하면 빌림 검사기가 프로그램을 거부한다.

원래 코드가 통과한 이유는 빌림 검사기가 state.cache와 state.template이 서로 다른 필드임을 추적해 동시 접근이 안전하다고 판단했기 때문이다. 그러나 뮤텍스가 끼어들면 검사기에게는 MutexGuard 구조체에 대한 불투명한 접근만 보인다. 가변 빌림이 상태를 수정하는 동안 콜백이 같은 상태를 읽어 데이터 경합이 생길 가능성을 배제할 수 없으니 거부하는 것이다. 흔한 해법은 MutexGuard를 한 번 역참조해 그 뒤의 구조체를 다시 빌리는 것이다. 이렇게 하면 원래와 같은 내장 참조가 만들어져 검사기가 다시 접근이 겹치지 않음을 볼 수 있다. 맨드리는 이런 우회가 직관적이지 않으며, 바로 이런 복잡성이 러스트의 가파른 학습 곡선에 일조한다고 지적했다. MutexGuard 같은 사용자 정의 포인터가 내장 참조처럼 동작한다면 훨씬 나을 것이다.

'플레이스'를 사용자 코드에 노출하다

포인터 타입은 도처에 있으면서 저마다 조금씩 다른 의미를 지닌다. 생 포인터는 안전하게 역참조할 수 없고, MaybeUninit는 쓸 수는 있어도 읽을 수는 없다. 이런 다양성은 러스트의 유연함을 보여주지만, 동시에 모든 포인터 타입을 아우르는 단일 설계를 어렵게 만든다. 언어 팀이 Nadrieril과 베노 로신(Benno Lossin)의 작업을 바탕으로 택한 해법은 컴파일러 내부 개념인 '플레이스(place)'를 사용자 코드에 노출하는 것이다. 플레이스는 C의 좌변값(lvalue)에 해당하는, 값을 읽고 쓸 수 있는 위치다. 포인터와의 차이는 플레이스가 state.cache처럼 컴파일 시점에 존재하는 추상 표현인 반면, 포인터는 그 플레이스를 런타임에 표현하는 한 가지 방식이라는 점이다.

설계의 핵심은 모든 스마트 포인터마다 새로운 핸들(handle) 타입을 만드는 것이다. 핸들은 흔히 같은 위치를 가리키는 안전하지 않은 포인터를 감싼 래퍼에 지나지 않는다. 컴파일러가 프로그램이 참조하는 플레이스를 나타내는 핸들을 자동으로 생성하면, 사용자는 그 핸들 타입에 트레이트를 구현해 빌림 검사기가 커스텀 스마트 포인터를 어떻게 다룰지 지정할 수 있다. 기존 Deref·DerefMut보다 유연한 제어다. 예를 들어 WritePlace 트레이트는 특정 핸들을 어떻게 쓸 수 있는지 검사기에 알려주고, SAFE가 false이면 해당 쓰기를 안전하지 않은 연산으로 취급하게 한다. 트레이트 구현 자체가 unsafe인데, 잘못 구현하면 검사기가 오판해 건전성이 깨질 수 있기 때문이다.

읽기를 다루는 ReadPlace 외에 더 흥미로운 것은 ProjectPlace와 BorrowPlace다. 전자는 구조체를 담은 플레이스를 그 필드의 플레이스로 바꾸는 방법, 즉 MutexGuard를 state.template 접근 시 MutexGuard으로 투영하는 방법을 알려준다. 후자는 내장 참조의 &가 하는 일, 즉 주어진 플레이스를 빌려 새 스마트 포인터를 만드는 방법을 인코딩한다. BorrowPlace의 새 포인터 생성 문법은 아직 논의 중이다. 기존 & 기호를 쓸 수도 있지만 혼동과 타입 추론 문제가 우려되고, @ 기호 제안도 완전히 환영받지는 못한다. 문법이 무엇으로 정해지든, BorrowPlace는 검사기가 포인터를 안전하게 다루는 데 필요한 정보를 모두 담는다. 덕분에 내장 &T의 동작도 같은 메커니즘으로 정의할 수 있어, 사용자에게는 참조처럼 동작하는 스마트 포인터 작성 예시가, 표준 라이브러리 관리자에게는 기존의 반직관적 동작을 문서화할 자리가 생긴다.

실무적 의미와 남은 과제

이 설계가 MutexGuard에 적용되면 앞서의 render_page() 예제는 우회 없이 통과한다. 맨드리는 이를 "예제 입장에서는 작은 코드 변경이지만 큰 의미론적 전환"이라고 표현했다. 부수 효과도 있다. 현재는 참조 뒤의 값에는 패턴 매칭이 되지만 스마트 포인터 뒤의 값에는 역참조·재빌림 없이는 불가능한데, BorrowPlace가 노출하는 정보면 컴파일러가 그런 패턴 매칭을 안전하게 구현할 수 있다. 언어 팀이 새 기능에서 중시하는 조합성(composability) 측면에서도, 핸들과 플레이스는 컴파일러가 이미 언어를 이해하는 방식을 드러낼 뿐이라 "아름답게 조합된다"는 평가다.

다만 플레이스와 핸들을 라이브러리에 노출하는 작업은 여전히 프로토타입 단계다. 맨드리는 이 설계가 모든 사용 사례에서 통하도록 도와달라며, 설계 문서를 검토하고 기이한 의미론을 가진 스마트 포인터 예시를 보태 달라고 청중에게 요청했다. 다음으로 다듬을 부분은 오류와 진단 메시지다. 이상적으로는 사용자가 새로 추가된 트레이트를 오류 메시지에서 보지 않아야 하며, 내장 참조처럼 문제를 직접 설명하는 메시지가 나와야 한다. 실무자 입장에서 보면, 당장 안정화된 기능으로 쓸 수 있는 것은 아니고 표준 라이브러리가 어떻게 활용하느냐에 따라 체감 효과가 달라질 전망이다.

질의응답에서는 적용 범위를 가늠하는 질문이 이어졌다. 값의 소유권을 가져오면서 구성 요소로 분해하는 파괴적 패턴 매칭도 지원되느냐는 질문에 맨드리는 해당 핸들에 VariantPlace 트레이트를 구현하면 가능하다고 답했다. 핸들의 모든 연산을 지원하려면 구현할 연산이 여섯에서 여덟 개 정도 있고 그중 하나라는 설명이다. 열거형(enum) 전반에도 통하느냐는 질문에는 잠시 허공을 응시한 뒤 머뭇거리며 길게 "그렇다"고 답했다. 열거형에서 일반적으로 필드를 투영하는 것은 아마 불가능하겠지만, 예컨대 Option 안의 필드를 투영해 그 필드 값의 Option을 얻는 식의 탐색적 작업은 진행 중이라고 했다. "답은 모르지만 흥미로운 질문"이라는 그의 말과 함께 세션 시간이 끝났다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://lwn.net/Articles/1096028/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...