Почему в Rust не рекомендуется принимать &String, &Vec и &Box в качестве аргументов функций?
Почему &String, &Vec и &Box — это плохой выбор для аргументов функций
Короткий ответ: принимая &String, &Vec или &Box, вы навязываете вызывающему коду конкретный тип, который требует дополнительной аллокации и не позволяет использовать более общие типы данных. Правильный подход — принимать ссылки на срез (&str для строк, &[T] для векторов) или просто &T для упакованных значений.
Проблема с &String против &str
Когда вы пишете:
fn print_length(s: &String) {
println!("{}", s.len());
}
Вы говорите: «Эта функция может работать только со ссылкой на String». Но что, если у вас есть &str (строковый литерал) или Cow<str>? Вы не сможете передать их без преобразования. Например:
fn main() {
let s1 = String::from("hello");
let s2 = "world"; // это &str
print_length(&s1); // работает
// print_length(&s2); // ОШИБКА: expected &String, found &&str
}
Вам пришлось бы делать print_length(&s2.to_string()) — это лишняя аллокация!
С &str такого ограничения нет:
fn print_length(s: &str) {
println!("{}", s.len());
}
fn main() {
let s1 = String::from("hello");
let s2 = "world";
print_length(&s1); // работает — автоматическая ссылка &String -> &str
print_length(s2); // работает — &str
}
Почему это работает? Потому что String реализует типаж Deref<Target=str>, и Rust автоматически выполняет приведение при заимствовании. То есть &String неявно становится &str. Это называется Deref coercion.
Проблема с &Vec против &[T]
Аналогичная ситуация с векторами:
// Плохо
fn sum_vec(v: &Vec<i32>) -> i32 {
v.iter().sum()
}
// Хорошо
fn sum_slice(v: &[i32]) -> i32 {
v.iter().sum()
}
С &[i32] вы можете передать:
&Vec<i32>— через Deref- Срез массива
&[1, 2, 3] - Срез части вектора:
&my_vec[1..3] - Любой другой тип, реализующий
AsRef<[i32]>
Проблема с &Box против &T
С Box то же самое: Box<T> реализует Deref<Target=T>. Принимая &Box<T>, вы заставляете вызывающего иметь именно Box. А если у него просто T на стеке? Нельзя передать. Правильно — принимать &T.
// Плохо
fn process_box(b: &Box<dyn SomeTrait>) { ... }
// Хорошо
fn process_trait(b: &dyn SomeTrait) { ... }
// или даже обобщённо:
fn process_generic<T: SomeTrait>(b: &T) { ... }
Когда это всё же может быть оправдано?
Есть редкие случаи, когда вам нужно именно &String или &Vec:
- Когда вы вызываете метод, который есть только у
String(например,as_mut_str) — но это почти всегда говорит о плохом дизайне. - Когда вы работаете с
RcилиArcи вам нужно сохранить ссылку на конкретный тип для подсчёта ссылок.
Практическое правило
Запомните простое правило:
- &String → &str
- &Vec<T> → &[T]
- &Box<T> → &T
Это делает ваши функции более гибкими, не теряя производительности. Deref coercion работает автоматически, так что вызывающему коду не нужно ничего менять.
<<Альтернативный взгляд: когда можно использовать &String и &Vec
Хотя общая рекомендация — избегать &String, &Vec и &Box в сигнатурах, есть несколько ситуаций, где это может быть оправдано:
1. Когда вам нужен доступ к методам, специфичным для конкретного типа
Например, String имеет метод capacity(), которого нет у &str. Если ваша функция действительно использует такие методы, то &String может быть уместен. Но лучше пересмотреть дизайн: возможно, стоит принимать &mut String или использовать обобщённый подход с трейтами.
2. Внутренние функции в модуле
Если функция не является частью публичного API и вызывается только с String внутри одного модуля, то использование &String не навредит. Однако даже в этом случае лучше привыкнуть к &str — это улучшит читаемость и упростит рефакторинг.
3. Производительность при очень частых вызовах
Теоретически, Deref coercion добавляет крошечные накладные расходы при каждом вызове. На практике это настолько ничтожно, что не стоит беспокоиться. Только если вы пишете высоконагруженный код с миллионами вызовов в секунду, можно задуматься о микрооптимизациях, но сначала измерьте!
Вывод
Правило «не принимайте &String, &Vec, &Box» — это рекомендация, а не жёсткое правило. Следуйте ей в 95% случаев, но если есть веская причина отступить — сделайте это осознанно и задокументируйте причину в комментарии.
Ответов пока нет.
Войдите чтобы оставить ответ