スライスにトレイトを実装する時の注意点

に公開

Take home message

スライスに何かしらのトレイトを実装しようとするのなら、[T]&[T]&mut [T]に実装しようってコトで一つ

どういうこと?

例として以下のような想定としてConsoleにDumpするようなトレイトがあったとして

trait Dump{
    fn dump(&self);
}

こいつをスライスに実装させようとする。このとき&[T]にのみ実装した場合以下のようになる

impl<T: Debug> Dump for &[T] {
	fn dump(&self) {
		println!("&[T]");
        for elem in self.iter() {
            println!("{elem:?}")
        }
	}
}

この場合、以下のような呼び出しは成功する

fn ref_dump(item: &(impl Dump + ?Sized)) {
	item.dump()
}

fn dump(item: impl Dump) {
	item.dump();
}

fn main() {
	let vec = vec![1, 2, 3, 4, 5];
	let slice = vec.as_slice();
    
	ref_dump(&slice);
	dump(slice);
}

しかし、以下のような呼び出しはコンパイルエラーが発生する。

fn main() {
	let vec = vec![1, 2, 3, 4, 5];
	let slice = vec.as_slice();

	ref_dump(slice);
}


error[E0277]: the trait bound `[{integer}]: Dump` is not satisfied
  --> src/main.rs:41:11
   |
41 |     ref_dump(slice);
   |     -------- ^^^^^ the trait `Dump` is not implemented for `[{integer}]`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `Dump` is implemented for `&[T]`
note: required by a bound in `ref_dump`
  --> src/main.rs:29:26
   |
29 | fn ref_dump(item: &(impl Dump + ?Sized)) {
   |                          ^^^^ required by this bound in `ref_dump`

概略、[T]に対する、Dumpの実装が無いよと怒れる。

これを解決するには[T]に対する実装をする必要がある。で実際実装したのが以下

impl<T: Debug> Dump for [T] {
	fn dump(&self) {
		println!("[T]");
		for elem in self.iter() {
			println!("{elem:?}");
		}
	}
}

こうすることで、全ての呼び出しに対応することが出来る

fn main() {
	let vec = vec![1, 2, 3, 4, 5];
	let slice = vec.as_slice();

	println!("ref_dump(slice);");
	ref_dump(slice);

	println!();
	println!("ref_dump(&slice);");
	ref_dump(&slice);

	println!();
	println!("dump(slice);");
	dump(slice);
}

Output
ref_dump(slice);
[T]
1
2
3
4
5

ref_dump(&slice);
&[T]
1
2
3
4
5

dump(slice);
&[T]
1
2
3
4
5

&mut [T]を実装しないとどうなるのか?

これで大方問題は無いんだけど、此処で例えば以下のような呼び出しをしちゃったとする

fn main() {
	let mut vec = vec![1, 2, 3, 4, 5];
	let mut_slice = vec.as_mut_slice();
	let mut_slice = vec.as_mut_slice();

	println!();
	println!("ref_dump(mut_slice);");
	ref_dump(mut_slice)
}

ref_dumpを呼び出すと[T]として解釈されるのでこれはうまくいく。

けれど以下のような実装は失敗する。

println!();
println!("dump(mut_slice);");
dump(mut_slice);

error[E0277]: the trait bound `&mut [{integer}]: Dump` is not satisfied
  --> src/main.rs:66:7
   |
66 |     dump(mut_slice);
   |     ---- ^^^^^^^^^ the trait `Dump` is not implemented for `&mut [{integer}]`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the following other types implement trait `Dump`:
             &[T]
             [T]
note: required by a bound in `dump`
  --> src/main.rs:39:20
   |
39 | fn dump(item: impl Dump) {
   |                    ^^^^ required by this bound in `dump`


ここは素直に、&mut [T]に対して、Dumpを実装してないから実行できないよってことになる。

println!();
println!("ref_dump(&mut_slice);");
ref_dump(&mut_slice);

error[E0277]: the trait bound `&mut [{integer}]: Dump` is not satisfied
  --> src/main.rs:70:11
   |
70 |     ref_dump(&mut_slice);
   |     -------- ^^^^^^^^^^ the trait `Dump` is not implemented for `&mut [{integer}]`
   |     |
   |     required by a bound introduced by this call
   |
note: required by a bound in `ref_dump`
  --> src/main.rs:35:26
   |
35 | fn ref_dump(item: &(impl Dump + ?Sized)) {
   |                          ^^^^ required by this bound in `ref_dump`
help: consider removing the leading `&`-reference
   |
70 -     ref_dump(&mut_slice);
70 +     ref_dump(mut_slice);
   |

こっちは&mut [T]の参照を与えてるので、&外せば先の[T]として評価できるから&抜いたら?といわれているけど、結局のところは&mut [T]に対するDump実装が存在しないのが根本的なエラーの原因となる。

impl<T: Debug> Dump for &mut [T] {
    fn dump(&self) {
        println!("&mut [T]");
        for elem in self.iter() {
            println!("{elem:?}");
        }
    }
}

こいつをまとめて実行すると出力込みで以下の通り。

fn main() {
	let mut vec = vec![1, 2, 3, 4, 5];
	let slice = vec.as_slice();

	println!("ref_dump(slice);");
	ref_dump(slice);

	println!();
	println!("ref_dump(&slice);");
	ref_dump(&slice);

	println!();
	println!("dump(slice);");
	dump(slice);

	let mut_slice = vec.as_mut_slice();

	println!();
	println!("ref_dump(mut_slice);");
	ref_dump(mut_slice);

	println!();
	println!("dump(mut_slice);");
	dump(mut_slice);
}

Output
ref_dump(slice);
[T]
1
2
3
4
5

ref_dump(&slice);
&[T]
1
2
3
4
5

dump(slice);
&[T]
1
2
3
4
5

ref_dump(mut_slice);
[T]
1
2
3
4
5

dump(mut_slice);
&mut [T]
1
2
3
4
5

実装を寄せる方法

今回は実験的かつ、どっちが呼ばれたのかを明示するために各々に独立した実装を与えた。しかし実際にはどちらの実装もほぼ同じなので、一方に実際の実装を行い、他方ではそれを呼び出すような実装を行った方が望ましい。

その場合には以下のような手法が考えられる

[T]側に寄せる

impl<T: Debug> Dump for [T] {
	fn dump(&self) {
		for elem in self.iter() {
			println!("{elem:?}");
		}
	}
}

impl<T: Debug> Dump for &[T] {
	fn dump(&self) {
        //<[T]>::dump(*self);
		(**self).dump()
	}
}

impl<T: Debug> Dump for &mut [T] {
	fn dump(&self) {
		//<[T]>::dump(*self)
		(**self).dump()
	}
}

&[T]側に寄せる

impl<T: Debug> Dump for [T] {
	fn dump(&self) {
        //<&[T]>::dump(&self);
		(&self).dump();
	}
}

impl<T: Debug> Dump for &[T] {
	fn dump(&self) {
		for elem in self.iter() {
			println!("{elem:?}");
		}
	}
}

impl<T: Debug> Dump for &mut [T] {
	fn dump(&self) {
        <&[T]>::dump(&self)
	}
}

まとめ

どちらか片方だけ実装していると思いがけずコンパイルエラーになることがある。

なので可能であればできるだけ両方実装しておいた方が汎用性が高くなるんじゃないかな

Discussion