Почему в Rust не рекомендуется принимать &String, &Vec или &Box в качестве аргументов функций?
Проблема: излишнее ограничение и потеря гибкости
Основная причина, по которой не рекомендуется принимать &String, &Vec или &Box — это нарушение принципа универсальности. Когда вы пишете функцию, которая принимает &String, вы автоматически требуете, чтобы вызывающий код владел именно String, а не, например, &str (строковый срез) или String, полученную из другой библиотеки.
В Rust тип String — это владеющий тип (owned type), а &str — это заимствованный срез. Если ваша функция принимает &String, то для передачи строкового литерала или среза вам придётся сначала создать String, что приводит к ненужному копированию данных в кучу. Сравните:
// Плохо: принимает только &String
fn print_string(s: &String) {
println!("{}", s);
}
// Хорошо: принимает &str
fn print_str(s: &str) {
println!("{}", s);
}
fn main() {
let owned = String::from("hello");
let slice = "world"; // это &str
print_string(&owned); // работает
// print_string(slice); // ОШИБКА: ожидается &String, получен &str
print_str(&owned); // работает (автоматическое приведение)
print_str(slice); // работает
}
То же самое справедливо для Vec и Box. &Vec<T> можно заменить на &[T] (срез), а &Box<T> — на &T.
Почему это работает? Автоматическое приведение (Deref coercion)
В Rust реализован механизм Deref coercion (приведение через трейт Deref). Благодаря ему:
&Stringавтоматически преобразуется в&str&Vec<T>— в&[T]&Box<T>— в&T
Это значит, что если ваша функция принимает &str, вы можете передать ей как строковый срез, так и ссылку на String. Вы получаете максимальную гибкость без потери производительности.
Когда можно использовать &String или &Vec?
Есть два законных случая:
- Когда вам нужно изменить содержимое через метод, который принимает
&mut selfи определён только для конкретного типа. Например,Vec::pushработает только с&mut Vec, а не со срезом. Но в этом случае вы принимаете&mut Vec, а не&Vec. - Когда вы пишете обобщённый код с трейтами. Например, если вам нужно потребовать, чтобы тип реализовывал
AsRef<str>илиDeref<Target=str>.
Производительность
С точки зрения производительности &String и &str идентичны — оба занимают 8 байт (указатель + длина в случае среза, или указатель + ёмкость + длина в случае String?). На самом деле &String — это ссылка на структуру из трёх полей (ptr, len, cap), а &str — это fat pointer (ptr + len). Но разница в косвенности минимальна и не является главной причиной.
Главная причина — это эргономика и совместимость. Используя &str или &[T], вы делаете свою функцию пригодной для большего числа ситуаций, не требуя от вызывающего кода лишних преобразований.
Итог
Всегда предпочитайте:
&strвместо&String&[T]вместо&Vec<T>&Tвместо&Box<T>
Исключение — если вам действительно нужен доступ к специфическим методам владеющего типа (например, Vec::capacity или String::as_mut_vec), но такие случаи редки.
Альтернативный взгляд: трейты и обобщения
Есть ещё один подход, который даёт максимальную гибкость — использовать обобщённые функции с трейтами. Вместо того чтобы принимать конкретный тип или срез, можно потребовать, чтобы тип реализовывал нужный трейт:
use std::fmt::Display;
fn print_it(s: impl Display) {
println!("{}", s);
}
fn main() {
let my_string = String::from("hello");
let my_str = "world";
let my_i32 = 42;
print_it(&my_string); // работает
print_it(my_str); // работает
print_it(my_i32); // тоже работает!
}
Этот подход ещё более универсален, чем &str, потому что позволяет передавать любые типы, реализующие Display. Однако он может быть избыточным, если вам нужны только строки или срезы.
Кроме того, вы можете использовать трейт AsRef:
fn print_as_ref(s: impl AsRef<str>) {
println!("{}", s.as_ref());
}
fn main() {
let s = String::from("hello");
print_as_ref(s); // работает
print_as_ref("world"); // работает
}
Это особенно полезно, когда вы пишете библиотеки и хотите, чтобы пользователи могли передавать как String, так и &str без лишних преобразований. Но для большинства простых случаев &str и &[T] — это золотая середина между гибкостью и читаемостью кода.
Ответов пока нет.
Войдите чтобы оставить ответ