질문의 배경
Rust에서 타입은 종종 다른 값을 캡슐화합니다(예: Box, Rc) 그리고 개발자들은 C++에서 포인터처럼 내부 값에 투명하게 접근하기를 원했습니다. 이를 위해 Deref(trait)와 DerefMut(trait) 및 메서드와 연산자를 통한 편리한 접근을 위한 자동 역참조 로직이 추가되었습니다.
문제
포인터와 스마트 포인터가 언제 어떻게 자동으로 전개되는지에 대한 혼란은 컴파일 오류, 메서드의 예상치 못한 동작, 때로는 불필요한 복사 또는 차용으로 인한 비효율적인 코드로 이어질 수 있습니다.
해결
Deref trait는 자신의 역참조 규칙을 정의할 수 있게 해줍니다(예: Box<T>는 Deref의 구현 덕분에 T처럼 동작함). 자동 역참조 시스템은 메서드 및 연산자 호출 시 Deref 타입 참조를 "전개"하려고 시도합니다. 이를 통해 다음과 같이 쓸 수 있습니다:
use std::rc::Rc; fn print_len(s: &str) { println!("{}", s.len()); } let s: Rc<String> = Rc::new("hello".into()); print_len(&s); // auto-deref: &Rc<String> -> &String -> &str
주요 특징:
* 연산자 사용 시 작동하며, 참조 타입의 패턴 매칭 시에도 작동합니다.개발자가 "기대하는" 대로 auto-deref가 항상 발생하는가?
아니요: deref coercion은 메서드 호출이나 Deref Target을 통해 다른 타입의 참조가 필요한 경우에만 발생합니다. 모든 표현식에서 작동하지 않으며, 예를 들어 패턴 매칭에서는 작동하지 않습니다.
deref coercion이 차용 및 소유의 규칙을 어지럽힐 수 있는가?
아니요. Deref coercion은 차용 규칙을 완전히 준수합니다 - Rust의 소유 안전성을 위반하여 두 개의 mutable 참조를 얻는 것은 불가능합니다.
T: Deref<Target=U>인 경우 &T에서 &U로의 auto-deref가 가능한가, 그리고 두 타입이 명시적으로 연결되지 않았다면?
네, &U를 기대하는 함수 호출 시 &T를 전달하면 T가 Deref<Target=U>를 구현하는 경우 자동 변환이 발생합니다.
코드 예:
struct Wrapper(String); impl std::ops::Deref for Wrapper { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } fn takes_str(s: &str) {} let w = Wrapper("mytext".into()); takes_str(&w); // auto-deref: &Wrapper -> &String -> &str
개발자가 자신의 스마트 포인터를 구현했지만 Deref 구현을 추가하지 않고 그 타입이 일반 값처럼 동작하기를 기대했습니다.
장점:
타입이 컴파일되고 내부 값에 명시적으로 접근할 수 있습니다.
단점:
메서드 호출 시 auto-deref가 작동하지 않으며, 표준 라이브러리 및 서드파티 라이브러리 함수 사용이 불편합니다.
사용자 정의 래퍼 구조체가 Box와 유사하게 Deref 및 DerefMut를 구현하여 모든 표준 함수와의 매끄러운 통합이 가능합니다.
장점:
편리함, 언어의 대부분 기능 에서의 투명한 작동, 코드의 청결함과 부드러운 통합.
단점:
타겟 타입이 불분명할 경우 Deref 인터페이스가 과도하게 복잡해질 위험이 있습니다.