4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

俺が知ってるrustのvec

Posted at

vecについてまとめていきます。
ゆるく更新していきます。

先に参考

とりあえずこれは知ってた

fn main() {
    let mut v = Vec::new();
    v.push(0);
    v.push(1);
    v.push(2);
    println!("{:?}", v);
}
[0, 1, 2]

mut(可変)にしてpushする方法やねん。
ただ、サイズが何となくわかっているなら、Vec::with_capacityが使えます。
説明は省きますが、あらかじめ要素数を指定しておくと早くなります。

with_capacityを用いても、サイズは可変です。
あくまでパフォーマンスの改善につながるのです。メモリの割り当て方によるものです。

fn main() {
    let mut v = Vec::with_capacity(3);
    v.push(0);
    v.push(1);
    v.push(2);
    println!("{:?}", v);
}
[0, 1, 2]

注意する点は、vecも配列同様、型が固定です。
Enumを使えば表現できる型が増えますが、それは別の話ってことで。
つまり、こういうのはダメ!〇刑! ってことです。

fn main() {
    let mut v = Vec::new();
    v.push(0);
    v.push(1);
    v.push('2');
    println!("{:?}", v);
}
error[E0308]: mismatched types
    --> src\main.rs:5:12
     |
3    |     v.push(0);
     |     -      - this argument has type `{integer}`...
     |     |
     |     ... which causes `v` to have type `Vec<{integer}>`
4    |     v.push(1);
5    |     v.push('2');
     |       ---- ^^^^ expected integer, found `char`
     |       |
     |       arguments to this method are incorrect
     |

文字が入っているから怒られてしまいました。残念。
逆にこれもNG

fn main() {
    let mut v = Vec::new();
    println!("{:?}", v);
}
error[E0282]: type annotations needed for `Vec<T>`
 --> src\main.rs:2:9
  |
2 |     let mut v = Vec::new();
  |         ^^^^^
  |
help: consider giving `v` an explicit type, where the type for type parameter `T` is specified
  |
2 |     let mut v: Vec<T> = Vec::new();
  |              ++++++++

For more information about this error, try `rustc --explain E0282`.

型がわがんねって、言われましたね。 言われた通りに直せばよいのです。

fn main() {
    let mut v: Vec<i32> = Vec::new();
    println!("{:?}", v);
}

まぁ...まさかと思いますが
「エラーはVec<T>って書いてあるんだから、

fn main() {
    let mut v: Vec<T> = Vec::new();
    println!("{:?}", v);
}

だろ?」なんて言わないでくださいね。
ジェネリクスの説明が必要になって大変なんですから。(だれも言ってない...よね?)

マクロ?なにそれ

こっからちょっと本題に入る

fn main() {
    let v = vec![0, 1, 2]
    println!("{:?}", v);
}
[0, 1, 2]

!はマクロという機能です。 これは、端的に言えばコンパイルする前にコードを書き換えてくれる優れものです。 このコードはおそらく

let v = {
    let mut v = Vec::with_capacity(3);
    v.push(0);
    v.push(1);
    v.push(2);
    v
};

というコードに変換されるはずです。

ちなみに、{}は式の一つです。 スコープ分けられます。 うへー便利な時代になったねぇ。

ちなみにこのvec!マクロ、結構有能で

fn main() {
    let v = vec![0; 3]
    println!("{:?}", v);
}
[0, 0, 0]

0が3つで初期化されましたね。
これは通常の配列でも使えるテクニックですが、こんな感じに初期値を決めて楽に作ることもできます。ヤッタネ!

俺が知ってるvec!マクロの欠点

さて...便利ですね。で済ませたかったのですが、少し話しておかなければいけないことがあります。

このマクロ、cloneトレイトが実装されている必要があるのです。
トレイトとは、簡単に例えるとこうです!
「普通自動車免許を持つ者は、技量はともあれ普通自動車を運転できる」
つまり、免許みたいなものです。 これがないと技量の有無関係なしにクローン出来ないのです。
とにかく、cloneトレイトが実装されいている型でないとvec!マクロは使えない、そう解釈しておきましょう。 え?今使っている変数が実装しているトレイトを知りたいって?
うーん...知らね!(基本型はcloneトレイトの上位互換であるcopyトレイトが実装されています。ほかにも説明すると長くなってしまいますでの、ここでは割愛します。ご了承くださいな)

トレイトがわからないならこの記事はここまで見れば十分です。これ以上はvecに関する内容から外れますので...

しかしこれが、災いを起こすことがあります。参照カウンタRc・Arcの時です。

これらのクローンは参照を返します。(実態・中身のコピーをしてくれない)
なので、このコードは端的に言いましょう。「意味を持ちません!残念!」

use std::rc::Rc;

fn main() {
    let v = vec![Rc::new(0); 3];
    println!("{:?}", v);
}
[0, 0, 0]

「は?なんで意味を持たないの? 0が三つ並んでんじゃないか。」
その0、すべて別物の変数ですか? まさか一つずらしたらすべてずれるなんてことありませんよね?

「知っておこう」

今日は、参照カウント変数を三つ入れた配列のうち、一つの数字を変えたらどうなるかについて、知っておこう(早口)

use std::{cell::RefCell, rc::Rc};

fn main() {
    // vecを宣言する
    // rcの中身は普通不変である。そのためRefCellを使って
    // 可変値にできるようにした。
    let v = vec![Rc::new(RefCell::new(0)); 3];
    // borrow_mutを残すとtwo borrowになってエラーになる。
    // これはmut参照を一つしか置けないのと同じことである。
    // なので、スコープを分け、このスコープ内のみで有効にする必要がある。
    {
        // アドレスをクローンする。 こうしないと書き換えれない。
        let i = Rc::clone(&v[0]);
        // borrow_mut mut権限をくだしゃい!
        let mut i = i.borrow_mut();
        // 変えてしまおうこんなもの
        *i = 3;
    }
    // println!マクロのデバック表示では表現できないのでforで手動
    for i in v {
        let i = Rc::clone(&i);
        // mutは不要なので普通にもらってくる
        let i = i.borrow();
        // いざ、表示!
        println!("{}", i);
    }
}
3
3
3

うーーーーん...じゃぁ!
(終)


では、ポインタを表示して視覚化してみましょう。

use std::rc::Rc;

fn main() {
    let v = vec![Rc::new(0); 3];
    println!("{:p}", v[0]);
    println!("{:p}", v[1]);
    println!("{:p}", v[2]);
}
0x21cb2a3ff00
0x21cb2a3ff00
0x21cb2a3ff00

なんてことだ、もう助からないゾ❤すべてが同じアドレスじゃないか。

ちなみにboxとか別のcloneが実装された型にすると

fn main() {
    let v = vec![Box::new(0); 3];
    println!("{:p}", v[0]);
    println!("{:p}", v[1]);
    println!("{:p}", v[2]);
}
0x18394eec400
0x18394eec420
0x18394eec3e0

ちゃんとアドレスがずれてますわね。さすがですわ。

これに関しては、次の「イテレーター」を使ったり、forで頑張ればなんとかなります。

イテレーターは知らなかった

javaのデザインパターンを見ていて「なんやねんこれ何の役に立つんや」ってずっと思ってた。
やっと理解したよ。やったね。

fn main() {
    let v:Vec<i32> = (0..3).collect();
    println!("{:?}", v)
}
[0, 1, 2]

まず秒で突っ込みたいのは型を指定しているところです。
だって右みりゃわかるじゃん。 って思いません?
思わない方はもう少し下にスクローール!

このcollectっていうのがどうやら頻繁に型を指定しなきゃいけないものの一つらしいです。 というのもそもそもcollectが、Vec以外にも適用できるかららしい。
(ここら辺はそのうち書きます多分)

それ以外の点を一つ一つ解読していきましょう。

Range()は知ってた

(0..3)はどっかで見覚えのある表現です。

for i in Range(0,3):
    print(i)

こいつです。 pythonで見ないことはないほど出てくる奴です。
Rangeというのはrustにもあって、それの表現方法が(..)って書き方です。
例えば(0..4)の場合、0,1,2,3 という4つの数字がイテレーター形式で表現されるわけです。

じゃあイテレーターってなんだよ! 

汎用的な言語でイテレーターなしとイテレーター有りを比べてみましょう。

// イテレーターなし
for(int i=0; i<3; i++) {
    println(i);
}

実際のこのコードが動く言語を知らないのですが、
想定している出力はこうです!

0
1
2

イテレーターがある場合、

while(iter.hasNext()) {
    println(i.next());
}

こんな感じになります。 実装を書いてないので、このコードは動きませんがね...
例えば紙芝居です。 next()メソッドは紙芝居の次のページに移ります。
紙芝居が終わっているか(最後のページを過ぎたか)はhasNext()が伝えてくれます。
これらはiterという変数が持つ"状態"によって管理されるわけです。

struct iter {
    source: &Vec<_>
    index: usize,
    hasNext: bool,
}

これは簡易的に表現した、「状態を持つ構造体」です。 まず、sourceには元の配列、indexには今見ているページ(ポインタ)、そしてhasNextは次があるかどうかを示すわけです。

これにメソッド「next」を実装すればいいのです。 どういうメソッドにすればいいでしょうか?
コードは書きませんが、

  • 今ページが指す値を返す
  • indexをインクリメントする
  • indexがsource.lenより多いならhasnextをfalseに変更する

図にするとこんな感じ
fig1.png

左が配列だと思ってください。 ポインタはイテレーターのことで、上でいうindexの値です。
コードに直すなら

iter.index = 1;
&v[iter.index];

こんな感じです。

イテレーターの中身はここまでにして、とにかく「順番に何かを行う」という操作をするにはこのイテレーターとかいう機能は大変有能なわけです。知 ら な か っ た。

例えばfor_each

fn main() {
    (0..3).for_each(|x| {
        println!("{}", x);
    });
}
0
1
2

forのイテレーターバージョンです。 多言語だといろいろメリットがありますが、rustだと
「(実質)一行でforが表現できる」などですかね。
ただし、rustは標準でforがイテレーター専用なので...🤔
と勝手に思っていますね。 (それいっちゃpyもそう)

今サラッと出てきた|x| {}というのはいわゆる無名関数とかいう奴です。
そしてfor_eachはコールバック関数なわけです。
javascriptとかでよく見た光景ですね。
簡単にコードにすると

fn for_each(&self, func: fn(T)) {
    for i in &self {
        func(i);
    }
}

こんな感じです。 引数selfはpythonなどと同様なので置いといて...
fn(T)を受け入れる引数funcは、書いてある通り関数を受け入れるわけです。
引数funcは変数funcであり、変数funcは関数funcなわけです。なので実行できます。

コールバック関数については、jsとかpythonとかやっていればわかると思います。
c++からの方は関数ポインタを使うらしいです。javaは関数テンプレートらしいです。 わたしゃしらん。

それと同時に出てくるのがこの無名関数こと「クロージャ」なわけです。

ほかにも

fn main() {
    (0..10).filter(|x| x % 2 == 0).for_each(|x| {
        println!("{}", x);
    });
}

filterの中身は x%2==0 。これはboolを返す式ですね。偶数ならtrueになるわけです。
trueのみを取ってくれるイテレーターです。なのでこれの出力は

0
2
4
6
8

となるわけです。
ほかにも

use std::rc::Rc;

fn main() {
    let x: Vec<_> = (0..3).map(|_| Rc::new(0)).collect();
    x.into_iter().for_each(|x| {
        println!("{:p}:{}", x, x);
    });
}
0x17101b84380:0
0x17101b84040:0
0x17101b84060:0

唐突にやってくるのは本題です。 まずvecとクロージャの引数が_(アンダーバー)になりました。
これは使わねぇこんなんって言ってます。それだけです。
型の指定も、あくまでVecであることが重要であり、その中身はどうでもいい(予測可能)ということだそうです。

次にmapです。 これは一見for_eachのように見えますが、戻り値(今回はRc::new(0))で新しいイテレーターが作られるというものです。 単純に値をずらしたり、二乗にしてみたりすることができます。

fn main() {
    (0..4)
        .map(|x| 2_i32.pow(x as u32))
        .for_each(|x| println!("{}", x));
}
1
2
4
8

数値は_i32と置くだけで型を表現できるらしいです。 シランカッター!
powは累乗ですね。引数がu32、つまり符号なしです。今回はasで型を変換(キャスト)してます。

mapについて理解できました。 into_iter()はイテレータを返すメソッド!

ヨシ、本題だ!!!

先の復習です。まずvec!マクロはcloneによって実装されるので

use std::rc::Rc;

fn main() {
    let v = vec![Rc::new(0); 3];
    println!("{:p}", v[0]);
    println!("{:p}", v[1]);
    println!("{:p}", v[2]);
}
0x21cb2a3ff00
0x21cb2a3ff00
0x21cb2a3ff00

という、同じアドレスを指す意味のない配列が出てきます。
これの解決方法は何でしょうか?
簡単です。map使いましょう。

use std::rc::Rc;

fn main() {
    let x: Vec<_> = (0..3).map(|_| Rc::new(0)).collect();
    x.into_iter().for_each(|x| {
        println!("{:p}:{}", x, x);
    });
}
0x17101b84380:0
0x17101b84040:0
0x17101b84060:0

ね?アドレス変わってるでしょ?

mapはクローンを使ってない(一々newしてくれる)から、アドレスが変わります。
ゆえに実体も変わります。 

これが、俺の知ってるrustのvecでした。 現場からは以上です、僕は右に帰ります。失礼します。

(←おうち)
(中山→)
(秋葉原→)
(どっかの本社→)

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?