質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

2回答

1029閲覧

【Rust】into_iter() の所有権

kekemoto

総合スコア34

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2018/09/23 07:26

編集2018/09/23 13:56

前提・実現したいこと

rust

1let set: HashSet<char> = ['a', 'b'].into_iter().collect();

発生している問題・エラーメッセージ

let set: HashSet<char> = ['a', 'b'].into_iter().collect(); | ^^^^^^^

a collection of type std::collections::HashSet<char> cannot be built from an iterator over elements of type &char

分からないこと

into_iter()で move しているはずなのに&charを返されるのが分からないです。
配列の中身がcharではなくi32なら上手くいきます。Copy トレイトの有無かとも思いましたが、charに Copy トレイトが実装されていないのは変だと思いますし、i32 のリファレンスを見たら Copy トレイトが実装されてないようです。

上記のどれかが誤った認識だと思うのですが、どれが誤った認識なのか分かりません……

追記・修正

i32 では出来ませんでした。
自分が見ていたサンプルではVecに対してinto_iter()をしていました。それだと出来るようです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

こちらで確認したところ、配列の中身をi32にした場合でも同じエラーが出ました。(rustc 1.28.0)

まず、元が[char; 2]の配列なのでarrayの定義を参照したところ、 [T; 2] はIntoIteratorトレイトを実装していなくて、実装しているのは &'a [T; 2]&'a mut [T; 2] になるようです。
なのでinto_iterが返す具体的な型は Iter<char>IterMut<char> になり、少し変ですね。
https://doc.rust-lang.org/std/primitive.array.html#method.into_iter

例えば Vec<T> に対してinto_iterを呼んだ場合、ちゃんと IntoIter<T> になっているようです。
https://doc.rust-lang.org/std/vec/struct.Vec.html#method.into_iter

実際に let set: HashSet<char> = (vec!['a', 'b']).into_iter().collect(); で試してみると、ちゃんとコンパイルできました。

arrayの説明の中では

Arrays of sizes from 0 to 32 (inclusive) implement the following traits if the element type allows it:

  • Debug
  • IntoIterator (implemented for &[T; N] and &mut [T; N])

...

というのが書いてありますね。
Rust Bookでも、「配列とベクタのどちらを使うか迷った時はベクタにしておけば良い」という記述がありましたし、今回の例もそうするのが良いと思います。

なぜ配列が格納する値から所有権を奪えない仕様になっているのかは何か理由がありそうです。
関連するissueを見つけましたが、英語に疎く説明できそうにないので、リンクだけ貼っておきます。
https://github.com/rust-lang/rust/issues/25725

投稿2018/09/23 09:14

編集2018/09/23 09:16
spinylobster

総合スコア23

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kekemoto

2018/09/23 14:11

`array`の`into_iter()`が実際には所有権をmoveせずに、借用や参照を行い、`iter()`や`iter_mut()`の動きしか出来ないならば、`into_iter()`を実装すべきではないと思います(実際にこうやって間違えるし……) メソッドがないエラーならば、なんかの理由でarrayは実装が難しいんだなと思えるのですが。 といった旨をissueに書こうかと思ったのですが、英語が分からないのか知識が足りないのか、issueがどういう結論に至ったのか分からず……
Eki

2018/09/23 23:22 編集

以下は私の意見にすぎないのですが。 IntoIterator はあくまで「イテレータに変換できる」ことを表すトレイトであって、「所有権をもつイテレータに変換できる」という意味ではありません。また、配列 [T; N] は IntoIterator を実装していないので、配列はイテレータにできません。 &[T; N] に対する IntoIterator の実装は &Vec<T> に対して IntoIterator が実装されているように、適切だと思います。 したがって、今回の問題は、メソッド呼び出し構文 (ドット) が、必要に応じて参照をつけたり外したりしてしまうことによって &[T; N] に対する IntoIterator を呼び出してしまうことで顕在化するものです。 配列の参照に対しても IntoIterator を実装しない、ということをすると、次のようなコードはコンパイルできなくなります。 for i in &[1, 2, 3] { ... } なぜなら for は「IntoIterator を実装しているもの」を受け取るからです。また IntoIterator を実装しないことは「イテレータにできない」ことになるので、配列に .iter() 他のメソッドを用意するのも不自然になります。ですから結果的に、配列の各要素をイテレートする手段がなくなってしまうのではないかと思います。
qnighy

2018/09/24 01:05

https://github.com/rust-lang/rust/pull/49000 にあるように互換性の問題があって先送りにされています。IntoIteratorが実装されれば&charではなくcharが帰るようになりますが、それでは逆に&charを期待していたコードが動かなくなってしまいます。 この互換性の問題を解消するには、まずcraterで実際の影響範囲を調べること、それからclippyなどのリントを用意することでユーザーへの注意を促すことが必要ですが、どちらも途中のまま実施されてないようです。声を上げれば優先度は上がるかもしれないですね。
kekemoto

2018/09/24 06:13 編集

> IntoIterator はあくまで「イテレータに変換できる」ことを表すトレイトであって、「所有権をもつイテレータに変換できる」という意味ではありません。 改めてドキュメントを読み直したところ、その通りのようですね…… https://doc.rust-lang.org/std/iter/index.html#for-loops-and-intoiterator ただそれと同時に、iter() , iter_mut() , into_iter() を所有権の違いによって使い分けているのも事実のようです。 https://doc.rust-lang.org/std/iter/index.html#the-three-forms-of-iteration https://doc.rust-lang.org/std/collections/index.html#iterators だから into_iter() の所有権の挙動はオカシイけど、実装しないという選択はできない。そのジレンマ故に現状があるのかもですね。 この問題の原因は into_iter() メソッドに対して「 forループできる、イテレータに変換できる」という意味と、「所有権を move してイテレータに変換する」という意味の二つを重ねているからだと思います。 「所有権を move してイテレータに変換する」というのを iter_move() とか別物にするべきだったと思いました。
guest

0

本題の前に一つ疑問が...

配列の中身がcharではなくi32なら上手くいきます。

次のコードを試してみましたが、同様のエラーが出ました。純粋に気になるので、どのようなコードで試されたか教えていただけますか...?

rust

1use std::collections::HashSet; 2 3fn main() { 4 let set: HashSet<i32> = [1, 2].into_iter().collect(); 5}

原因

本題です。これが成功しない原因はご推察の通り余計な参照がついていることです。 HashSet<char> は、 char のイテレータからは作れますが &char のイテレータから作ることはできません。

ではなぜ char ではなく &char が返ってくるのか。実は into_iter() は別に全くムーブすることを要求しません。

into_iter()IntoIterator トレイトにより提供されます。現在対象となっている ['a', 'b'][char; 2] ですが、 ドキュメント によると、[char; 2] に対する直接の実装はありません。しかし次の実装はあります。

rust

1impl<'a, T> IntoIterator for &'a [T; 2] { type Item = &'a T; ... }

ということでレシーバが勝手に参照に変換され、この実装を使用して &'a char を生成するイテレータを作ります。

into_iter() に所有権をムーブする印象があるのは、おそらく Vec<T> に関して into_iter() が所有権を移動する挙動をとるからだと思いますが、これは実際に、わざわざ

rust

1impl<T> IntoIterator for Vec<T> { type Item = T; ... }

というふうに所有権をとるような実装をしているからです。

解決

おっしゃる通り charCopy を実装していますので、明示的にデリファレンスしてやることで型を合わせることができます。

rust

1let set: HashSet<char> = ['a', 'b'].into_iter().map(|x| *x).collect();

あるいは、コメント欄で qnighy さんからご指摘を頂いたように cloned() を使う方法もあります。これはイテレータを受け取って、各要素を clone() したものを生成するイテレータを返す関数です。ドキュメントによると、少なくとも整数に関しては上と等価です。こちらの関数を使うのが Rust 的には自然と言えるでしょう。

rust

1let set: HashSet<char> = ['a', 'b'].into_iter().cloned().collect();

Copy トレイトが i32 などに書かれていない問題ですが、改めて 最新のドキュメント をあたってみるときちんと対応されていました。あなたがご覧になっているリファレンスは URL を良く見ると Rust 1.25 時点のものです。 Google などで検索して辿りつくページはなぜかこのバージョンがすごくまちまちで、いつも困ります。ここは常に最新なので、ふだんはこちらをブックマークして、ドキュメント内の検索機能を使うとか、ターミナルに rustup doc --std と入力することであなたのパソコン内にあるオフラインのドキュメントを開くこともできます。

原因について調べると こちら が見つかりました。Copy トレイトはコンパイラによって特別扱いされるトレイトですので、普通のトレイトとは訳が違います。ドキュメントは ソースコードから 生成されますが、プリミティブについてはコンパイラ内で扱われるビルトインなものだったので明示的な実装がなく、ドキュメントにはのっていなかったようです。現在は明示的な実装が置かれたため、ドキュメントにのりました。

投稿2018/09/23 08:53

編集2018/09/24 03:11
Eki

総合スコア429

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kekemoto

2018/09/23 14:13

mapの参照外し?のやり方いいなぁと思いました。 Copy トレイトが載ってなかったり、なんか案外ドキュメントてきとうなんですかね?
Eki

2018/09/23 21:27

Copy トレイトが載っていない問題について調べたので、追記しました。
qnighy

2018/09/24 01:10

&TのイテレーターをTに昇格するにはclonedを使うほうがイディオマティックかと思います。Cloneの実装がCopyと同等に速いという完璧な保証はないですが、ほとんどの場合ではCopyと同等になると思います。
Eki

2018/09/24 02:57

そうですね、ありがとうございます。どちらにするか悩んだ挙句上のようにしましたが、両方書いておくべきでした。追記しておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問