Рассмотрите различие между размером значения и размером данных, на которые оно ссылается. Почему этот код допускает &str, но не допускает локальную переменную типа str?
fn length(s: &str) -> usize {
s.len()
}
fn main() {
let text = String::from("Rust");
let view: &str = &text;
println!("{}", length(view));
// let invalid: str = *"Rust";
}
str — это динамически размещаемый тип (DST): его размер во время компиляции неизвестен. Поэтому значение str нельзя хранить непосредственно как локальную переменную, параметр по значению или поле обычной структуры. &str допустим, потому что сама ссылка имеет известный размер и содержит указатель на данные вместе с их длиной.
В Rust нужны были типы для последовательностей переменного размера: строковых срезов, массивов срезов и других данных, размер которых определяется во время выполнения. Для этого язык разделяет конкретное значение и дескриптор, через который к нему обращаются.
Такой подход позволяет безопасно работать с динамическими данными без обязательного копирования. Тип str описывает сами UTF-8-данные без фиксированной длины, а &str — безопасное заимствованное представление этих данных.
Компилятор должен знать размер локальной переменной, чтобы разместить её на стеке или корректно передать по значению. Для str этот размер неизвестен до выполнения, поэтому объявление переменной такого типа нарушает требование о размере типа.
При этом запрет не означает, что со str нельзя работать. Риск устраняется использованием указателя или другого контейнера с известным размером; важно лишь выбрать подходящую форму владения и времени жизни.
&str — это ссылка на динамически размещаемые данные. В обычной реализации она является широкой ссылкой и содержит два компонента: адрес начала последовательности байтов и её длину. Размер самой ссылки известен, даже если размер данных по адресу неизвестен.
В примере String владеет буфером, а &text создаёт заимствованный срез этого буфера. Переменная view имеет фиксированный размер, а str за ней не перемещается и не копируется. Вызов length(view) использует длину, сохранённую в ссылке.
Строковый литерал имеет тип &'static str. Выражение *"Rust" обращается к данным типа str, но не превращает их в значение фиксированного размера. Поэтому строку нельзя сохранить напрямую как str.
Для владения данными можно использовать String или, когда нужен именно динамический строковый тип, Box<str>. String хранит управляемый буфер и обычно резервную ёмкость, а Box<str> владеет ровно строковым срезом. Оба варианта дают переменную с размером, известным компилятору: размерен сам контейнер или указатель-контейнер, а не содержимое.
Ограничение распространяется и на обобщённые типы: параметр T обычно подразумевает T: Sized. Если функция должна принимать ссылку на любой тип, включая str или [u8], ограничение снимают записью T: ?Sized, например fn show<T: ?Sized>(value: &T). Это разрешает динамический размер самого T, но ссылка всё равно остаётся фиксированного размера.
Допустим, обработчику нужно принимать текст без копирования. Вариант с String удобен, если функция должна забрать владение, но он требует передачи владельца или перемещения значения. Вариант с &str не копирует данные и принимает как String, так и строковые литералы, однако время жизни заимствования ограничивает использование результата.
Можно выбрать Box<str>, если функция должна владеть строкой без дополнительной ёмкости String. Его минус — менее удобное изменение содержимого: для наращивания строки обычно проще использовать String. Поэтому для чтения текста без передачи владения выбирают &str, для изменяемого владения — String, а Box<str> применяют, когда нужна компактная владеющая строка фиксированного после создания размера.
Почему String является Sized, если его содержимое имеет переменный размер?
String — это контейнер фиксированного размера, обычно содержащий указатель на буфер, длину и ёмкость. Переменным является только внешний буфер, поэтому сам тип String удовлетворяет Sized и может передаваться по значению.
Почему &str обычно занимает два машинных слова, а &u8 — одно?
Ссылка на u8 знает адрес одного объекта, чей размер фиксирован. Ссылка на str должна дополнительно хранить длину среза, поэтому является широкой ссылкой. Аналогичный принцип применяется к ссылкам на другие DST, например &[T] и &dyn Trait, хотя состав метаданных у них различается.
Почему функция fn f<T>(x: &T) не всегда принимает &str?
Неявное ограничение для параметра типа — T: Sized, поэтому такая сигнатура фактически требует размеренный T. Для поддержки str нужно написать fn f<T: ?Sized>(x: &T). Ограничение ?Sized не делает значение T допустимым по значению: оно лишь разрешает использовать динамически размещаемый тип за подходящим указателем, например за ссылкой.