[T]とかstrの使い方
前口上
Rustのスライス型には汎用の&[T]
と、文字列の&str
がある。
割と以前から、[T]
やstr
という&
無しの定義に使い道はあるのかしらん?と疑問に思っていたんだけど、今般こー言うときに使うのねということが少し解ったのでまとめておこうかなって。
仕込み
毎度ながら結構作為的な事例になるのでその辺ご了承の程。
ConsoleにDumpを実行するtraitを定義するとしたら概ね以下のようになるかなって
pub trait Dump {
fn dump(&self);
}
このtraitをSliceに適用しようとした場合、dump(&self)
の通り、Associate先の参照を得る形となる。此処で、たとえば以下のように実装したとする
impl<T: Debug> Dump for &[T] {
fn dump(&self) {
for elem in self.iter() {
println!("{elem:?}")
}
}
}
fn dump(item: &impl Dump) {
item.dump()
}
と、ここまでは良いんだけど、実際呼び出そうとしたとき
fn main() {
let vec = vec![1, 2, 3, 4, 5, 6];
dump(&vec.as_slice())
}
と、dump(&vec.as_slice())
とスライスの参照即ちここでは&&[i32]
をこさえることになるので割るか無いけどなんとなく上長打よねってことになる。
[T]
を使う
ということで、実装を少し変えてみよう
impl<T: Debug> Dump for [T] {
fn dump(&self) {
for elem in self.iter() {
println!("{elem:?}")
}
}
}
fn dump(item: &(impl Dump + ?Sized)) {
item.dump()
}
となる。
dump関数の引数に&[T:Debug]
を使おうとした場合、当然のことながらスライスは?Sizedなので、?Sized
境界を付与しておかなければならない。逆に先の場合はスライスへの参照なのでこの境界は不要となっている。
但し、呼び出しの方が
fn main() {
let vec = vec![1, 2, 3, 4, 5, 6];
dump(vec.as_slice())
}
このように余計な参照が不要になってすっきりする
切口上
&str
に対する実装も以下のようになる
impl Dump for str {
fn dump(&self) {
println!("{self}")
}
}
impl Dump for &str {
fn dump(&self) {
println!("{self}")
}
}
Sliceに対して何かしらのtraitをimpl仕様としたとき概ね&self
を取るメソッドが内包されていることが殆どじゃないかなと思う。このとき、&[T]
や&str
に対する実装とした場合、先に観察したとおりスライスの参照に対する実装となり、スライスそのものへの実装とすることが出来ない。
スライスそのものへのAssociateとしたい場合に[T]
やstr
を使うと言うことが理解できた。
追記 ~やっぱり両方実装した方が幸せかなって~
これまでの実装をまとめて,以下のようにしてみた
use std::fmt::Debug;
pub trait Dump {
fn dump(&self);
}
impl<T: Debug> Dump for &[T] {
fn dump(&self) {
println!("&[T] implements");
for elem in self.iter() {
println!("{elem:?}")
}
}
}
impl<T: Debug> Dump for [T] {
fn dump(&self) {
println!("[T] implements");
for elem in self.iter() {
println!("{elem:?}")
}
}
}
fn dump(item: &(impl Dump + ?Sized)) {
item.dump()
}
fn val_dump(item: impl Dump) {
item.dump()
}
fn main() {
let vec = vec![1, 2, 3, 4, 5];
let slice = vec.as_slice();
println!("slice.dump();");
slice.dump();
println!("----------\n");
println!("dump(slice);");
dump(slice);
println!("----------\n");
println!("val_dump(slice)");
val_dump(slice);
println!("----------\n");
}
これの出力結果が以下
cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
Running `target\debug\iterable.exe`
slice.dump();
[T] implements
1
2
3
4
5
----------
dump(slice);
[T] implements
1
2
3
4
5
----------
val_dump(slice)
&[T] implements
1
2
3
4
5
----------
ここから解るように、slice.dump();
とfn dump(item: &(impl Dump + ?Sized))
への呼び出しはimpl<T: Debug> Dump for [T]
こっちの実装が使われている一方、val_dump(slice);
の呼び出しは、impl<T: Debug> Dump for &[T]
こちらの方が使われる結果となっている。
このことから、可能であればどちらも実装しておいた方が自由度が高くなるんじゃないかなと考えた
fn val_dump(item: impl Dump)
は、見れば解るとおり、値をMoveさせる形で引数を取っている。結果、impl<T: Debug> Dump for [T]
の実装だけ提供している場合は、引数として、[T]
を要求されることになり、そんなもんは存在できない*(と僕は現時点で考えている)*のでコンパイルが通せなくなる。
以上のことから、スライスに対してTraitをAssociateしようとした場合、[T]
またはstr
だけで良いのか、それとも&[T]
と&str
のみで良いのかそれとも両方の実装が必要とされるのか使われ方を想定しながら考慮する必要があるかなって
Discussion