Программирование RustВладение и заимствованиеRust-разработчик системного программного обеспечения

Что происходит с владением String, когда в функцию, ожидающую &str, передают ссылку на эту строку?

Что происходит с владением String, когда в функцию, ожидающую &str, передают ссылку на эту строку?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Владение String не передаётся: функция получает временное неизменяемое заимствование. При передаче ссылки Rust применяет deref coercion — преобразует &String в совместимый &str, сохраняя исходный объект у вызывающего кода.

Исторический контекст

В Rust владение должно оставаться явным, но повседневная работа со строками не должна требовать ручного извлечения среза из каждого String. Поэтому язык поддерживает безопасные неявные преобразования ссылок, включая переход от &String к &str через реализацию Deref.

Такой подход разделяет ответственность: вызывающая сторона владеет изменяемой строкой, а функция получает только необходимый ей доступ к последовательности UTF-8.

Постановка проблемы

String владеет буфером в куче, тогда как &str — это заимствованный вид на часть строковых данных. Если бы передача String в функцию для чтения перемещала значение, вызывающий код потерял бы доступ к строке после вызова.

Если вместо заимствования попытаться передать владение, функция должна была бы вернуть значение обратно либо уничтожить его. Это ухудшает интерфейс и может привести к ошибке использования перемещённого значения.

Подробное решение

Сначала вызывающий код создаёт ссылку &String. Затем Rust видит, что функция ожидает &str, и применяет deref coercion: благодаря Deref<Target = str> ссылка на String приводится к ссылке на строковый срез.

fn print_text(text: &str) { println!("{text}"); } fn main() { let text = String::from("ready"); print_text(&text); println!("{}", text.len()); }

Вызов не перемещает text: функция временно читает его содержимое. Заимствование действует до окончания вызова, после чего владелец снова может использовать строку.

Важно различать String и &str. String владеет выделенным буфером и может изменять его размер, а &str содержит только ссылку на UTF-8-данные и их длину. &str может указывать как на весь String, так и на его часть.

Неявное преобразование не означает автоматическую передачу владения из String в &str. Однако сама ссылка должна быть создана: в обычном вызове функции передача значения String вместо ссылки не превращается произвольно в &str; обычно требуется написать заимствование явно.

Ограничение сохраняется: пока существует активное неизменяемое заимствование, нельзя изменять String через изменяемый доступ. Это предотвращает одновременное чтение данных и изменение буфера, которое могло бы сделать ссылку недействительной.

Ситуация из практики

Сервис получает строку из конфигурации и передаёт её нескольким функциям, которым нужно только прочитать содержимое. Вариант с передачей String по значению прост, но перемещает владение и делает повторное использование неудобным. Вариант с клонированием сохраняет владение, но расходует дополнительную память и время.

Оптимальный вариант — принимать &str, а при вызове передавать &String. Это не создаёт копию буфера, позволяет функции работать и со строковым литералом, и со строкой, а владение остаётся у вызывающего кода. Компромисс заключается в том, что функция не может сохранить такую ссылку дольше времени жизни исходной строки без соответствующего ограничения времени жизни.

Что кандидаты часто упускают

Вопрос 1. Может ли функция, принимающая &str, изменить исходный String?

Нет. &str — неизменяемое заимствование, поэтому функция может читать данные, но не изменять их через полученную ссылку. Для изменения нужен &mut str или другой интерфейс с изменяемым заимствованием, причём в каждый момент не должно существовать конфликтующего чтения.

Вопрос 2. Создаёт ли переход от &String к &str новый строковый буфер?

Нет. Deref coercion меняет представление ссылки, а не копирует содержимое. Полученный &str указывает на тот же участок UTF-8-буфера и содержит длину среза; выделения памяти и клонирования строки не происходит.

Вопрос 3. Почему после вызова функции строку можно использовать, но нельзя изменить её во время вызова?

После завершения вызова заимствование прекращается, поэтому владелец снова получает обычный доступ. Во время вызова действует неизменяемое заимствование: изменение строки могло бы изменить или перераспределить её буфер, сделав читаемую функцию ссылку недействительной. Правила заимствования запрещают такой конфликт ещё на этапе компиляции.