나는 이미 여러 문맥에서 "팻 포인터"라는 용어를 읽었지만 그것이 정확히 무엇을 의미하고 Rust에서 언제 사용되는지 잘 모르겠습니다. 포인터는 일반 포인터보다 두 배 큰 것 같지만 이유를 모르겠습니다. 또한 특성 개체와 관련이있는 것 같습니다.
나는 이미 여러 문맥에서 "팻 포인터"라는 용어를 읽었지만 그것이 정확히 무엇을 의미하고 Rust에서 언제 사용되는지 잘 모르겠습니다. 포인터는 일반 포인터보다 두 배 큰 것 같지만 이유를 모르겠습니다. 또한 특성 개체와 관련이있는 것 같습니다.
답변:
"팻 포인터"라는 용어는 동적 크기 유형 (DST) (슬라이스 또는 특성 개체)에 대한 참조 및 원시 포인터를 참조하는 데 사용됩니다 . 팻 포인터는 포인터와 DST를 "완전하게"만드는 정보 (예 : 길이)를 포함합니다.
녹에서 가장 일반적으로 사용되는 유형은 하지 DSTS하지만, 컴파일 타임에 고정 된 크기를 가지고있다. 이러한 유형 은 Sized
특성을 구현 합니다 . (같은 동적 크기의 힙 버퍼를 관리에도 종류가 Vec<T>
) 있습니다 Sized
컴파일러가 바이트의 정확한 수를 알고로 Vec<T>
인스턴스가 스택에 소요됩니다. 현재 Rust에는 4 가지 종류의 DST가 있습니다.
[T]
및 str
)유형 [T]
(모든 T
)은 동적으로 크기가 조정됩니다 (특수한 "문자열 슬라이스"유형도 그렇습니다 str
). 그렇기 때문에 일반적으로 참조 뒤에 &[T]
또는 로만 표시됩니다 &mut [T]
. 이 참조는 소위 "팻 포인터"입니다. 점검 해보자:
dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());
이것은 (일부 정리와 함께) 인쇄합니다.
size_of::<&u32>() = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>() = 16
따라서 일반적인 유형 u32
에 대한 참조는 배열에 대한 참조 와 마찬가지로 8 바이트 크기 [u32; 2]
입니다. 이 두 가지 유형은 DST가 아닙니다. 그러나 [u32]
DST와 마찬가지로 이에 대한 참조는 두 배입니다. 슬라이스의 경우 DST를 "완료"하는 추가 데이터는 단순히 길이입니다. 따라서의 표현 &[u32]
은 다음과 같다고 말할 수 있습니다 .
struct SliceRef {
ptr: *const u32,
len: usize,
}
dyn Trait
)특성 개체로 특성을 사용할 때 (즉, 유형이 지워지고 동적으로 전달됨) 이러한 특성 개체는 DST입니다. 예:
trait Animal {
fn speak(&self);
}
struct Cat;
impl Animal for Cat {
fn speak(&self) {
println!("meow");
}
}
dbg!(size_of::<&Cat>());
dbg!(size_of::<&dyn Animal>());
이것은 (일부 정리와 함께) 인쇄합니다.
size_of::<&Cat>() = 8
size_of::<&dyn Animal>() = 16
다시 말하지만 일반 유형 &Cat
이므로 크기가 8 바이트에 불과 Cat
합니다. 그러나 dyn Animal
특성 개체이므로 동적으로 크기가 조정됩니다. 따라서 &dyn Animal
크기는 16 바이트입니다.
특성 개체의 경우 DST를 완료하는 추가 데이터는 vtable (vptr)에 대한 포인터입니다. 여기서 vtable과 vptr의 개념을 완전히 설명 할 수는 없지만이 가상 디스패치 컨텍스트에서 올바른 메서드 구현을 호출하는 데 사용됩니다. vtable은 기본적으로 각 메서드에 대한 함수 포인터 만 포함하는 정적 데이터 조각입니다. 이를 통해 특성 객체에 대한 참조는 기본적으로 다음과 같이 표현됩니다.
struct TraitObjectRef {
data_ptr: *const (),
vptr: *const (),
}
(이것은 추상 클래스에 대한 vptr이 객체 내에 저장되는 C ++와 다릅니다. 두 접근 방식 모두 장점과 단점이 있습니다.)
실제로 마지막 필드가 DST 인 구조체를 사용하여 자체 DST를 만들 수 있습니다. 그러나 이것은 다소 드문 경우입니다. 대표적인 예가 std::path::Path
.
사용자 지정 DST에 대한 참조 또는 포인터도 팻 포인터입니다. 추가 데이터는 구조체 내부의 DST 종류에 따라 다릅니다.
에서 RFC 1861 의 extern type
기능이 도입되었다. Extern 유형도 DST이지만 이에 대한 포인터는 팻 포인터 가 아닙니다 . 또는 더 정확하게는 RFC가 말한대로 :
Rust에서 DST에 대한 포인터는 가리키는 객체에 대한 메타 데이터를 전달합니다. 문자열과 슬라이스의 경우 이것은 버퍼의 길이이고, 특성 객체의 경우 이것은 객체의 vtable입니다. extern 유형의 경우 메타 데이터는 단순히
()
. 이것은 extern 유형에 대한 포인터가 a와 크기가 동일하다는 것을 의미합니다usize
(즉, "팻 포인터"가 아님).
그러나 C 인터페이스와 상호 작용하지 않는 경우 이러한 extern 유형을 다룰 필요가 없을 것입니다.
위에서 우리는 불변 참조의 크기를 보았습니다. 팻 포인터는 변경 가능한 참조, 변경 불가능한 원시 포인터 및 변경 가능한 원시 포인터에 대해 동일하게 작동합니다.
size_of::<&[u32]>() = 16
size_of::<&mut [u32]>() = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>() = 16