質問: Rustでrusqliteで与える引数の&の意味がわかりません。 以下のように conn.execute("INSERT INTO todo (text) VALUES (?)", &[&params.text])?; としている箇所がありますが、ここでの &[&params.text]の二つの&は何を意味しているのでしょうか? [params.text]というように&を両方削除してもコンパイルは通り、期待通りの動きをしていました。 &params.text は文字列型を文字列型の参照にして、それを[](配列?)で囲ってそれをさらに&で参照?が何をしているのかがよく理解できません。 (ドキュメント( )でも&[&ToSql]という形で記載されており、必要なのだろうと思いますが、&を削除しても動いたのが疑問になってしまいました。) (以下のサイトのコード ) ``` conn.execute("INSERT INTO todo (text) VALUES (?)", &[&params.text])?; ``` わかる方いましたらよろしくお願いいたします。
回答: > > ここでの &[&params.text]の二つの&は何を意味しているのでしょうか? > > > 変数名の前に`&`を付けると参照ができます。また、`&[ ... ]`はスライスを作ります。これらの実体はポインターの一種で、メモリーのどこか他の場所にある値を指しています。(なおRustには`Deref`としくみがあって、ある条件を満たすと、`&`を付けた参照からスライスを作ることができます。質問とは直接関係がないので説明は省略します) Rustには所有権という概念があります。これにより、ある変数の値を他の変数に代入すると、(多くの型では)その値の所有権が元の変数から他の変数へムーブ(移動)し、元の変数からはアクセスできなくなります。 ``` fn main() { let a = "Hello".to_string(); // aはString型 // aの内容を表示する println!("{}", a); // aの値をbに代入する。これにより文字列の所有権がaからbへムーブ(移動)する let b = a; // bはString型 // aの内容を表示しようとすると、コンパイルエラーになる println!("{}", a); // → error[E0382]: borrow of moved value: `a` (ムーブ済みの値 a の借用) ``` 一方、参照やスライスを使うと、元の値をムーブせずに、その値にアクセスすることができます。 ``` // 上のコードの続き let c = &b; // bは&String型(Stringの参照型) println!("{}", b); // エラーにならずHelloと表示される println!("{}", c); // エラーにならずHelloと表示される } ``` 参照やスライスにはこのような特徴があり、値をムーブしたくないときに便利に使えます。 所有権、参照、スライスについてはRustの公式ドキュメントの4章で丁寧に解説されていますので、そちらを参照してください。 ここに和訳があります。 * > > [params.text]というように&を両方削除してもコンパイルは通り、期待通りの動きをしていました。 > > > なぜ`&`を両方削除しても動いたかというと、rusqliteではRustのジェネリクスという仕組みを使って、`execute`メソッドなどが色々な型の引数を取れるようになっているからです。rusqliteのドキュメントに 例が載っており 、そこには `[2i32, 3i32]` も `&[&2i32, &3i32]` を含むいくつかの受け付け可能な型があります。 (ジェネリクスについても上の和訳ドキュメントで解説されています) では`[params.text]`と書かずに`&[&params.text]`と書くと何が嬉しいのかというと、先ほどの所有権のムーブの話が関係しています。 `[params.text]`では所有権のムーブが起こり、元の変数からは値にアクセスできなくなります。 ``` // ジェネリックな関数f。どんな型の引数も取れる fn f(param: T) {} fn main() { // aはString型 let a = "Hello".to_string(); // bは[String; 1]型(長さが固定の配列) // こう書くとaの持つ文字列の所有権が配列にムーブする let b = [a]; // ムーブしたのでもうaにはアクセスできない。コンパイルエラー println!("{}", a); // 関数fに引数としてbを渡す // bの持つ配列の所有権が、関数fにムーブする f(b); // ムーブしたのでもうbにはアクセスできない。コンパイルエラー println!("{:?}", b); } ``` 一方、`&[&params.text]`なら所有権がムーブしないので、元の変数から引き続きアクセスできます。 ``` // ジェネリックな関数f。どんな型の引数も取れる fn f(param: T) {} fn main() { // aはString型 let a = "Hello".to_string(); // bは&[&String]型(&Stringのスライス) // 参照なのでaの持つ文字列の所有権をムーブしないで済む let b = &[&a]; // まだaにアクセスできる println!("{}", a); // 関数fに引数としてbを渡す f(b); // まだbにアクセスできる println!("{:?}", b); } ``` `[params.text]`と`&[&params.text]`にはこのような違いがあり、所有権をムーブさせたくないときには後者が使えます。 **追記** コメント欄で以下のご指摘と質問を受けましたので、それらに対する回答を追記します。 1. スライスの作り方について。(`let b = &[&a];` としている箇所ですが、スライスは&変数名[num..num]という形という理解だったのですが、&[&変数名]の記法があるんでしょうか?) 2. 例示のコードではスライス型になってないのでは?(IDEだと後者は&[&std::string::String; 1] ... と表示されており、型が違いました) 3. `&[&a]`はあくまでrusqliteの引数の型に合わせるため?(単に所有権をムーブしたくない場合は参照でさえあればよく、&[&a]、&aや[&a]のいずれでも問題ないという理解であっているでしょうか?) 2はご指摘通りで、スライス型になってませんでした。例を作ったときの私のミスです。1の説明と合わせて、修正したコードを下に書きました。 3はその理解で合っています。 1について説明します。スライスを明示的に作るときは、ご指摘通り`&変数名[num..num]`という記法を使います。ただ、配列や`Vec`の全要素に対するスライスを作るときに毎回`&変数名[..]`と書くのは面倒なので、`&変数名`と書くだけで暗黙的に全要素を対象にしたスライスを作ることができます。この暗黙的なスライスの作成は`std::ops::Deref`というトレイトを使って実現されており、Deref coercion(Derefによる型強制、参照外しによる型強制)と呼ばれます。 ``` // ジェネリックなT型を要素とするスライスを引数に取る fn f2(params: &[T]) {} fn main() { // aはString型 let a = "Hello".to_string(); // 配列を作成する // bは[&String; 1]型(長さ1の&Stringの配列) let b = [&a]; // cは&[&String; 1]型(長さ1の&Stringの配列への参照) let c = &b; // bから明示的にスライスを作る。dは&[&String]型(スライス) let d = &b[..]; // bをスライス型へ型強制することで暗黙的にスライスを作れる // (Deref coercion) let e: &[&String] = &b; // dとeはf2の引数paramsと型が合うので、当然引数として渡せる f2(d); f2(e); // cは型が直接合わないが、paramsがスライスを要求していることで // b → eと同じくDeref coercionが働き、引数として渡せる f2(c); // 配列の作成から、b → eまでの変換を一気に行う f2(&[&a]); // もちろんb → dのように明示的にスライスを作ることもできる。 // が、結果は1つ上と同じなのでわざわざこう書く必要はない f2(&[&a][..]); } ``` コード内の`b`から`e`を作るところがDeref coercionによるスライスの作成になります。 そして、下から2つ目の`f2(&[&a])`がrusqliteで出てきた配列の作成からDeref coercionまでを一気に行う記法となります。 Derefトレイトのしくみや型強制について、詳しくは以下のドキュメントを参照してください。 * TRPL — Derefトレイトでスマートポインタを普通の参照のように扱う * Rust裏本 — 型強制