拡張トレイトを定義すべきでしょうか?

どのような場面で、フリー関数よりも拡張トレイトを優先すべきでしょうか?

// 著作権 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)がある場合、それらを 1 つの StrExt トレイトに まとめる方が、ユーザーがインポートする複数のフリー関数を用意するよりも すっきりしていることがよくあります。

  • トレードオフ: こうした利点がある一方で、単純な関数が 1 つだけの場合には、 専用の拡張トレイトはやりすぎかもしれません。どちらのアプローチでも追加の インポートが必要であり、見慣れたメソッド構文という利点が、完全なトレイト定義の ための定型コードに見合わないこともあります。