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
String
and&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
に型強制される。
&T
or&mut T
to&U
ifT
implementsDeref<Target = U>
&mut T
to&mut U
ifT
implementsDerefMut<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
T
implementsDeref<Target = U>
, andx
is a value of typeT
, then:
...
T
implicitly implements all the (immutable) methods of the typeU
. Deref in std::ops - More on Deref coercion - Rust
If
T
implementsDerefMut<Target = U>
, andx
is a value of typeT
, then:
...
T
implicitly 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,
Unsize
andCoerceUnsized
, are used to assist in this process and expose it for library use. The following coercions are built-ins and, ifT
can be coerced toU
with one of them, then an implementation ofUnsize<U>
forT
will 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
ただし、リファレンスにはスライスのメソッドは記載されていないので注意。