🐈

impl TraitとTrait boundの違い

2024/05/09に公開

結論

  • impl Trait: Trait bound(トレイト境界)の糖衣構文。シンプルに関数の引数や戻り値の型としてトレイトを定義したい場合に使われる。
  • Trait bound: ジェネリクス型を使うので複数の引数に同じ型を定義できたりする。指定したいトレイトの数が多い場合はこちらを使うと見やすい。

impl Traitの使用例

引数や戻り値に特定のトレイトを指定したい場合に使えます。

fn first_item(mut iter: impl Iterator<Item = i32>) -> Option<i32> {
    iter.next()
}

fn main() {
    let numbers = vec![1, 2, 3];
    let first = first_item(numbers.into_iter());
    println!("First item: {:?}", first);
}

上記の例だとIteratorトレイトが実装されている何らかの型を引数に取ることができます。

impl TraitはTrait boundのシンタックスシュガー(糖衣構文)ですので、Trait boundで書く場合は以下のようになります。

fn first_item<T: Iterator<Item = i32>>(mut iter: T) -> Option<i32> {
    iter.next()
}

fn main() {
    let numbers = vec![1, 2, 3];
    let first = first_item(numbers.into_iter());
    println!("First item: {:?}", first);
}

ここまでで示したコードは等価ですが、より複雑なトレイト境界を定義したい場合はTrait boundが有効になってきます。

Trait boundの使用例

例えばですが、複数の引数に同じトレイト境界を定義したい場合、impl Traitを使うと少し冗長になります。

use std::fmt::Display;

fn join_string(str1: impl ToString + Display, str2: impl ToString + Display) -> String {
    format!("{}{}", str1, str2)
}

fn main() {
    let str1 = "文字列1";
    let str2 = "文字列2";
    println!("{}", join_string(str1, str2))
}

trait boundを使う形で書けばジェネリクス型を活用できるため、簡潔に書くことができます。

use std::fmt::Display;

fn join_string<T: ToString + Display>(str1: T, str2: T) -> String {
    format!("{}{}", str1, str2)
}

fn main() {
    let str1 = "文字列1";
    let str2 = "文字列2";
    println!("{}", join_string(str1, str2))
}

他にもtrait boundの場合はwhere句を使って表現することもでき、多くのトレイト境界を定義する必要のあるシーンではコードを見やすくすることのできるメリットがあります。

fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

where句を使わない場合、以下のように一行に多くの要素が入り視認性が悪いような気がしますね。

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

まとめ

複数の引数にそれぞれ異なるトレイトを取るようにしたり、一つの引数に複数のトレイトが実装されている型を取りたいなんてケースの場合はTrait boundの書き方が使われがちな気がします。

逆に一つの引数に一つのトレイト実装を持つ型を取りたいというシーンではimpl Traitで書かれているのをよく見かけます。

あとはめっちゃ主観ですが

use futures::Future;

pub trait Client: Send + 'static + Clone {
    fn submit(&self) -> impl Future<Output = ()> + Send;
}

pub trait Client2: Send + 'static + Clone {
    fn submit<F: Future<Output = ()> + Send>(&self) -> F;
}

上のような非同期関数をトレイトで定義するシーンではimpl traitが使われていること多いような気がします。

参考:https://doc.rust-jp.rs/book-ja/ch10-02-traits.html

Discussion