Стоит ли определять трейт-расширение?
В каких сценариях следует предпочесть трейт-расширение свободной функции?
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
pub trait StrExt {
fn is_palindrome(&self) -> bool;
}
impl StrExt for &str {
fn is_palindrome(&self) -> bool {
self.chars().eq(self.chars().rev())
}
}
// против
fn is_palindrome(s: &str) -> bool {
s.chars().eq(s.chars().rev())
}
Главное преимущество трейтов-расширений — простота обнаружения.
-
Обнаруживаемость: методы-расширения легче найти, чем свободные функции. Языковые серверы (например,
rust-analyzer) предложат их, если вы введёте.после экземпляра внешнего типа. -
Цепочки вызовов: важное эргономическое преимущество трейтов-расширений — цепочки вызовов методов. Именно на этом основан трейт
Iterator, который позволяет писать плавные вызовы вродеdata.iter().filter(...).map(...). Добиться того же с помощью свободных функций было бы гораздо неудобнее (map(filter(iter(data), ...), ...)). -
Обобщения и
dyn: трейт можно использовать как ограничение трейта в обобщённом коде или как частьdyn Trait, тогда как свободная функция не всегда может использоваться в обобщённом контексте. -
Связность API: трейты-расширения помогают создать связный API. Если у вас есть несколько связанных функций для внешнего типа (например,
is_palindrome,word_count,to_kebab_case), объединить их в один трейтStrExtчасто удобнее, чем предоставлять пользователю несколько свободных функций для импорта. -
Компромиссы: несмотря на эти преимущества, специально написанный трейт-расширение может оказаться избыточным для одной простой функции. Оба подхода требуют дополнительного импорта, а привычный синтаксис методов может не оправдывать шаблонного кода полного определения трейта.