rs.nkmk.me

Rustで関数の仮引数の型をスライス(&[T], &str)にする

Posted: | Tags: Rust, ベクタ

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]);

sortreverseなど、要素数が変わらない処理はスライスのメソッドなので&mut [T]で問題ないが、要素数が変わるpushpopなどはベクタ固有のメソッド。関数内でそのような処理を行いたい場合は仮引数を&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]);

&strStringでも同様

文字列スライス&strと文字列Stringは、それぞれ、指し示すデータがUTF-8として有効であることが保証されているスライス&[u8]とベクタVec<u8>である。

したがって、&strStringにも&[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型強制、参照外し型強制)。

TDeref<Target = U>を実装していると&Tおよび&mut T&Uに、TDerefMut<Target = U>を実装していると&mut T&mut Uに型強制される。

Vec[T]へのDeref, DerefMutを実装しており、例えば&Vec<T>&[T]に型強制される。

同様に、StringstrへのDeref, DerefMutを実装しており、例えば&String&strに型強制される。

なお、TDeref<Target = U>およびDerefMut<Target = U>を実装していると、TU のすべての(イミュータブルおよびミュータブル)メソッドを暗黙のうちに実装する。

If T implements Deref<Target = U>, and x is a value of type T, then:
...

If T implements DerefMut<Target = U>, and x is a value of type T, then:
...

そのようなメソッドは、リファレンスの"Methods from Deref<Target = X>"のところに記載されている。

Unsized型強制

型強制の種類の一つが、Unsized coercions(Unsized型強制)。

Two traits, Unsize and CoerceUnsized, are used to assist in this process and expose it for library use. The following coercions are built-ins and, if T can be coerced to U with one of them, then an implementation of Unsize<U> for T will be provided:

ごく簡単にいうと、[T; N]Unsize<[T]>を実装しており、CoerceUnsized<&U> for &TCoerceUnsized<&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

ただし、リファレンスにはスライスのメソッドは記載されていないので注意。

関連カテゴリー

関連記事