Что означает знак вопроса в ограничении типа-параметра (where T: ?Sized)?
Что такое трейт `Sized` и почему он особенный?
В Rust каждый тип по умолчанию считается Sized, то есть его размер известен на этапе компиляции. Это необходимо для большинства операций — например, для хранения значения на стеке или передачи в функцию по значению. Однако есть типы, размер которых неизвестен во время компиляции: срезы ([T]), строковые срезы (str) и dyn-трейты (dyn Trait). Их называют DST (Dynamically Sized Types).
Вопросительный знак в ?Sized — это ослабление (relaxation) ограничения по умолчанию. Он говорит компилятору: "Этот тип-параметр не обязан быть Sized". Без ?Sized компилятор неявно добавляет Sized к каждому типу-параметру.
Разберём на примере
Представьте, что вы пишете функцию, которая принимает ссылку на значение и печатает его:
fn print_debug<T: Debug>(value: &T) {
println!("{:?}", value);
}
Здесь T: Debug автоматически означает T: Sized + Debug. Вы можете вызвать print_debug(&42) — всё работает. Но попробуйте передать ссылку на срез:
let slice: &[i32] = &[1, 2, 3];
print_debug(slice); // Ошибка компиляции!
Почему ошибка? Потому что slice имеет тип &[i32], где T = [i32] — это DST (размер не известен). Компилятор говорит: "Тип [i32] не реализует Sized".
Решение с ?Sized
Исправляем функцию:
fn print_debug<T: ?Sized + Debug>(value: &T) {
println!("{:?}", value);
}
Теперь T может быть как Sized (например, i32), так и DST (например, [i32] или str). Теперь оба вызова работают:
print_debug(&42); // T = i32 (Sized)
print_debug(&[1, 2, 3]); // T = [i32] (DST)
Когда нужно использовать ?Sized?
- Когда вы работаете с ссылками — функция принимает
&TилиBox<T>, и вы хотите разрешить DST. - При реализации трейтов для обобщённых типов — например, трейт
MyTraitможет быть реализован для[T]илиstr. - В стандартных трейтах —
Clone,Debug,Displayи другие часто используют?Sizedв своих реализациях для ссылок.
Важное правило
?Sized можно использовать только для последнего типа-параметра и только в контексте, где тип передаётся по ссылке или в умном указателе (Box, Rc, Arc). Нельзя создать переменную типа let x: [i32] — это ошибка, так как размер неизвестен. Но можно работать с &[i32] или Box<[i32]>.
Другие вопросительные знаки в ограничениях?
На данный момент ?Sized — единственный случай использования вопросительного знака в ограничениях типа-параметра. В будущих версиях Rust могут появиться другие (например, ?Send или ?Sync), но пока это не реализовано.
Итог: ?Sized — это способ сделать обобщённый код более гибким, позволяя работать с типами, размер которых неизвестен на этапе компиляции. Без него Rust по умолчанию требует, чтобы все типы были Sized.
Альтернативный взгляд: когда ?Sized не нужен
Если ваша функция всегда принимает конкретный тип, а не ссылку на него, то ?Sized не требуется. Например:
fn print_value<T: Debug>(value: T) {
println!("{:?}", value);
}
Здесь T передаётся по значению, поэтому T обязан быть Sized. Это логично — вы не можете положить на стек значение неизвестного размера.
Также стоит помнить, что ?Sized работает только с последним параметром трейта. Например, нельзя написать fn foo<T: ?Sized, U: Debug>(...) — это ошибка компиляции. Но если трейт сам определён с ?Sized, то можно:
trait MyTrait<T: ?Sized> { ... } // Ошибка!
trait MyTrait<T: ?Sized = dyn MyTrait> { ... } // Только в ночных сборках
Практический совет: Если вы пишете библиотеку и не уверены, будет ли кто-то использовать ваш код с DST, добавляйте ?Sized везде, где принимаете ссылку (&T). Это не повредит, но сделает код более универсальным. Для начинающих же лучше сначала писать без ?Sized — так компилятор будет точнее указывать на ошибки.
Ответов пока нет.
Войдите чтобы оставить ответ