Что такое "fat pointer" (жирный указатель) в Rust и зачем он нужен?
Что такое fat pointer и почему он "жирный"?
Отличный вопрос! Вы почти угадали. Fat pointer (жирный указатель) — это указатель, который хранит не только адрес памяти, но и дополнительную мета-информацию. В Rust есть два основных случая:
- Срезы (slices):
&[T]или&str - Трейт-объекты (trait objects):
&dyn TraitилиBox<dyn Trait>
Обычный указатель (например, &i32) занимает 8 байт на 64-битной системе — это просто адрес. Fat pointer занимает 16 байт (два машинных слова). Отсюда и название — он "жирнее" обычного.
Срезы: почему &[i32] — это fat pointer?
Когда вы пишете let slice: &[i32] = &[1, 2, 3];, переменная slice хранит:
// Структура fat pointer для среза:
struct SliceFatPtr {
ptr: *const i32, // указатель на первый элемент (8 байт)
len: usize, // длина среза (8 байт)
}
// Итого: 16 байт
Зачем нужна длина? Rust — безопасный язык. Если бы хранился только адрес, компилятор не мог бы проверить границы при обращении к slice[5]. Длина позволяет:
- Проверять выход за границы (panic при
slice[100]) - Итерироваться по срезу без внешнего счетчика
- Передавать срез в функции, не теряя информацию о размере
Пример, который показывает разницу:
fn main() {
let arr = [10, 20, 30, 40];
let slice: &[i32] = &arr[1..3]; // срез из 2 элементов
// Размеры в байтах:
println!("Размер &i32: {}", std::mem::size_of::<&i32>()); // 8
println!("Размер &[i32]: {}", std::mem::size_of::<&[i32]>()); // 16
}
Трейт-объекты: &dyn Debug — второй вид fat pointer
Когда вы используете &dyn Debug, Rust не знает конкретный тип во время компиляции. Но он должен знать, как вызвать метод fmt(). Для этого fat pointer хранит:
struct TraitObjectFatPtr {
ptr: *const (), // указатель на данные (8 байт)
vtable: *const (), // указатель на таблицу виртуальных методов (8 байт)
}
// Итого: 16 байт
vtable — это таблица, созданная компилятором, которая содержит адреса всех методов трейта для конкретного типа. Когда вы вызываете item.fmt(), Rust:
- Берёт vtable из fat pointer
- Находит в ней адрес метода
fmtдля вашего типа - Вызывает его, передавая данные по
ptr
Демонстрация:
use std::fmt::Debug;
fn print_debug(item: &dyn Debug) {
// item — fat pointer 16 байт
println!("{:?}", item);
}
fn main() {
let x = 42;
let s = "hello";
print_debug(&x); // &dyn Debug для i32
print_debug(&s); // &dyn Debug для &str
}
Обратите внимание: &dyn Debug занимает 16 байт, хотя &i32 — всего 8. "Лишние" 8 байт — это vtable, которая позволяет динамически диспетчеризовать вызовы методов.
Почему это важно?
- Производительность: fat pointer увеличивает размер передаваемых данных (16 байт вместо 8), но избавляет от необходимости хранить длину/методы отдельно.
- Безопасность: Rust не может позволить себе C-style "сырые" указатели без метаданных — это привело бы к ошибкам.
- Гибкость: трейт-объекты позволяют писать полиморфный код без дженериков.
Надеюсь, теперь стало понятнее! Fat pointer — это не магия, а просто пара (указатель + метаданные), упакованная в 16 байт.
<<Дополнение: как создать свой fat pointer?
Вы можете сами реализовать "жирный указатель", используя кортеж из двух полей. Но Rust делает это за вас для срезов и трейт-объектов. Интересный факт: fat pointer может быть только для ссылок (&) или умных указателей (Box, Rc). Сырые указатели (*const T) — всегда тонкие (8 байт).
Практический совет
Если вы работаете с FFI (интерфейс с C), помните: C не понимает fat pointer. Передавая срез в C-функцию, вы должны передать два отдельных параметра: указатель и длину:
// Rust
fn call_c(slice: &[u8]) {
unsafe {
c_function(slice.as_ptr(), slice.len()); // ptr + len отдельно
}
}
// C
void c_function(const uint8_t* data, size_t len);
Если вы попытаетесь передать &[u8] как один аргумент, C получит мусор, так как ожидает 8 байт, а получит 16.
Проверка размера в коде
Для любопытных — вот как проверить, является ли ваш тип fat pointer:
fn is_fat_pointer<T>() -> bool {
std::mem::size_of::<T>() == 2 * std::mem::size_of::<usize>()
}
fn main() {
println!("&[i32]: {}", is_fat_pointer::<&[i32]>()); // true
println!("&i32: {}", is_fat_pointer::<&i32>()); // false
println!("&dyn Debug: {}", is_fat_pointer::<&dyn std::fmt::Debug>()); // true
}
Этот трюк полезен для отладки, когда вы не уверены, что именно хранится в переменной.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги