Rustで関数の仮引数の型をスライス(&[T], &str)にする
Rustでは、ベクタの参照&Vec<T>を関数に渡したい場合、仮引数の型をスライス&[T]にするのが一般的。同様に、Vecとスライスに相当するStringと&strにおいても、&Stringを渡したい場合、仮引数の型を&strにすることが推奨されている。
In Rust, it’s more common to pass slices as arguments rather than vectors when you just want to provide read access. The same goes for
Stringand&str. Vec in std::vec - Slicing - Rust
Clippyでは、&Vec<T>や&Stringを仮引数の型にすると&[T]や&strにするように警告される。
以下のサンプルコードでは、説明のため、型推論によって省略できるときも明示的に型注釈を記述している場合がある。
関数の仮引数の型をスライス&[T]にするメリットと注意点
メリット: ベクタと配列の参照(&Vec<T>, &[T; N])をどちらも渡せる
仮引数の型をベクタの参照&Vec<T>とした場合、スライスや配列の参照を渡すとエラーになる。
fn print_first_element_vec(v: &Vec<i32>) {
println!("{}", v[0]);
}
let v: Vec<i32> = vec![100, 200, 300];
let a: [i32; 3] = [10, 20, 30];
print_first_element_vec(&v);
// 100
// print_first_element_vec(&v[1..]);
// error[E0308]: mismatched types
// print_first_element_vec(&a);
// error[E0308]: mismatched types
仮引数をスライス&[T]とした場合、ベクタと配列の参照(&Vec<T>, &[T; N])をどちらも渡せる。当然、スライスも渡せる。
fn print_first_element_slice(s: &[i32]) {
println!("{}", s[0]);
}
print_first_element_slice(&v);
// 100
print_first_element_slice(&v[1..]);
// 200
print_first_element_slice(&a);
// 10
これは、関数呼び出しにおいて、&Vec<T>, &[T; N]の値が&[T]に型強制(暗黙の型変換)されるから。ベクタからスライスへはDeref型強制で、配列からスライスへはUnsized型強制。詳細は後述。
このように、仮引数を&[T]とすることで、より広範な型に対応した関数とすることができる。
注意点1: 関数内で使用できるメソッドの違い
仮引数をスライス&[T]とした場合、当然、その関数内ではスライスに実装されているメソッドのみが使える。
ベクタにはDeref型強制によってスライスのメソッドが実装されているが、ベクタに固有のメソッドもある。
用途は限られているが、例えばベクタの容量を返すcapacityのように、ベクタに固有かつ参照から実行できるメソッド(引数が&self)を関数内で使いたい場合は、仮引数を&[T]ではなく&Vec<T>とする必要がある。
次に説明する可変参照から実行できるメソッド(引数がmut &self)はベクタに固有のものが多いので注意。
注意点2: 可変参照&mutの場合
仮引数を&mut [T]とした場合も、ベクタと配列の可変参照(&mut Vec<T>, &mut [T; N])をどちらも渡せる。
fn first_element_to_zero(s: &mut [i32]) {
s[0] = 0;
}
let mut v: Vec<i32> = vec![100, 200, 300];
let mut a: [i32; 3] = [10, 20, 30];
first_element_to_zero(&mut v);
assert_eq!(v, [0, 200, 300]);
first_element_to_zero(&mut a);
assert_eq!(a, [0, 20, 30]);
sortやreverseなど、要素数が変わらない処理はスライスのメソッドなので&mut [T]で問題ないが、要素数が変わるpushやpopなどはベクタ固有のメソッド。関数内でそのような処理を行いたい場合は仮引数を&mut Vec<T>とする。
fn push_zero(s: &mut Vec<i32>) {
s.push(0);
}
let mut v: Vec<i32> = vec![100, 200, 300];
push_zero(&mut v);
assert_eq!(v, [100, 200, 300, 0]);
&strとStringでも同様
文字列スライス&strと文字列Stringは、それぞれ、指し示すデータがUTF-8として有効であることが保証されているスライス&[u8]とベクタVec<u8>である。
したがって、&strとStringにも&[T]とVec<T>と同じことがいえる。
関数呼び出しにおいて、&Stringの値は&strに型強制(Deref型強制)される。不変参照を関数に渡したい場合、仮引数を&strとすると、&Stringも&strもどちらも渡せる。それらのスライスも渡せる。
fn print_first_char(s: &str) {
println!("{}", s.chars().next().unwrap());
}
let s_str: &str = "abc";
let s_string: String = String::from("xyz");
print_first_char(s_str);
// a
print_first_char(&s_string);
// x
print_first_char(&s_str[1..]);
// b
print_first_char(&s_string[1..]);
// y
文字数を増やしたり減らしたりするような操作はStringでしかできないので、可変参照に対して関数内でそのような操作を行いたい場合、仮引数は&mut Stringとする。
fn push_str_abc(s: &mut String) {
s.push_str("_abc");
}
let mut s_string: String = String::from("xyz");
push_str_abc(&mut s_string);
assert_eq!(s_string, "xyz_abc");
Rustにおける型強制
Rustにおける型強制については以下にまとめられている。
ここでは、型強制が起こる場所と、本記事内に登場したDeref型強制とUnsized型強制について簡単に紹介する。
Coercion sites
型強制はプログラム内の特定の場所でのみ起こる。そのような場所をCoercion sites(型強制サイト?)と呼ぶ。
関数呼び出しもCoercion sitesに含まれ、実引数が仮引数の型に型強制される。
Arguments for function calls
The value being coerced is the actual parameter, and it is coerced to the type of the formal parameter. Type coercions - Coercion sites - The Rust Reference
Deref型強制
型強制の種類の一つが、Deref coercions(Deref型強制、参照外し型強制)。
TがDeref<Target = U>を実装していると&Tおよび&mut Tが&Uに、TがDerefMut<Target = U>を実装していると&mut Tが&mut Uに型強制される。
&Tor&mut Tto&UifTimplementsDeref<Target = U>&mut Tto&mut UifTimplementsDerefMut<Target = U>
Type coercions - Coercion types - The Rust Reference
Vecは[T]へのDeref, DerefMutを実装しており、例えば&Vec<T>が&[T]に型強制される。
同様に、StringはstrへのDeref, DerefMutを実装しており、例えば&Stringが&strに型強制される。
なお、TがDeref<Target = U>およびDerefMut<Target = U>を実装していると、TはU のすべての(イミュータブルおよびミュータブル)メソッドを暗黙のうちに実装する。
If
TimplementsDeref<Target = U>, andxis a value of typeT, then:
...
Timplicitly implements all the (immutable) methods of the typeU. Deref in std::ops - More on Deref coercion - Rust
If
TimplementsDerefMut<Target = U>, andxis a value of typeT, then:
...
Timplicitly implements all the (mutable) methods of the typeU. DerefMut in std::ops - More on Deref coercion - Rust
そのようなメソッドは、リファレンスの"Methods from Deref<Target = X>"のところに記載されている。
- Vec in std::vec - Methods from Deref<Target = [T]> - Rust
- String in std::string - Methods from Deref<Target = str> - Rust
Unsized型強制
型強制の種類の一つが、Unsized coercions(Unsized型強制)。
Two traits,
UnsizeandCoerceUnsized, are used to assist in this process and expose it for library use. The following coercions are built-ins and, ifTcan be coerced toUwith one of them, then an implementation ofUnsize<U>forTwill be provided:
[T; n]to[T].- ...
Type coercions - Unsized Coercions - The Rust Reference
ごく簡単にいうと、[T; N]はUnsize<[T]>を実装しており、CoerceUnsized<&U> for &TやCoerceUnsized<&mut U> for &mut Tなどが実装されている(※実際はライフタイム注釈が付く)ため、&[T; N]や&mut [T; N]が&[T]や&mut [T]に型強制される。
型強制によって、配列からもスライスのメソッドを呼ぶことができる。
Arrays coerce to slices (
[T]), so a slice method may be called on an array. Indeed, this provides most of the API for working with arrays. array - Rust
ただし、リファレンスにはスライスのメソッドは記載されていないので注意。