Rustのstd::ops::Rangeの範囲表現

いろんな表現ができるんですね

// `()`で範囲指定ができる
assert_eq!((3..5), std::ops::Range { start: 3, end: 5 });

// 配列を作成し`sum()`することができる
assert_eq!(3 + 4 + 5, (3..6).sum());

let arr = [0, 1, 2, 3, 4];

// `[..]`はすべての配列要素を意味する
assert_eq!(arr[..], [0, 1, 2, 3, 4]);

// `[..3]`は先頭から2までのインデックスを意味する
assert_eq!(arr[..3], [0, 1, 2]);

// `[..=3]`は先頭から3までのインデックスを意味する
assert_eq!(arr[..=3], [0, 1, 2, 3]);

// `[1..]`は1から末尾までのインデックスを意味する
assert_eq!(arr[1..], [1, 2, 3, 4]);

// `[1..3]`は1から2までのインデックスを意味する
assert_eq!(arr[1..3], [1, 2]);

// `[1..=3]`は1から3までのインデックスを意味する
assert_eq!(arr[1..=3], [1, 2, 3]);

https://doc.rust-lang.org/std/ops/struct.Range.html

RustのBoxでデータをヒープ領域に割り当てる

Boxというのがちらほら出てくるが、これは対象のデータをヒープに割り当てるらしい。

ヒープとはなんぞやということで、それについてはこちらで解説されていた。まずメモリ管理にはスタックとヒープという用語がある。

スタックはとても高速で、Rustにおいてデフォルトでメモリが確保される場所です。 しかし、このアロケーションはひとつの関数呼び出しに限られた局所的なもので、サイズに制限があります。 一方、ヒープはより遅く、プログラムによって明示的にアロケートされます。 しかし、事実上サイズに制限がなく、広域的にアクセス可能です。

ということで、さらに下まで呼んでいくと、ヒープはスタックよりも長く残る仕掛けがあって、このおかげで異なる最終的にはDropトレイトによってアロケートされたメモリをデアロケートするとのこと。

で、これだけだとどんな時に使うのかイマイチだったので、さらにBox<T>はヒープのデータを指し、既知のサイズであるを読んでみたらわかりやすく書いてあった。

  • コンパイル時にはサイズを知ることができない型があり、正確なサイズを要求する文脈でその型の値を使用する時
  • 多くのデータがあり、所有権を転送したいが、そうする時にデータがコピーされないことを確認する時
  • 値を所有する必要があり、特定の型ではなく特定のトレイトを実装する型であることのみ気にかけている時

で、下まで読んでいくと具体的な説明がある。結構Rustでコードを書いているとコンパイラがBoxを使わなきゃだめだぞと怒ってくれていたような記憶がある(たぶん)。今後Boxを使わなきゃいけないシーンになったら、ああヒープに持っていかなきゃいけないからだねと理解しながらかけるので少し分かった気になった。

またVecやStringもヒープに割り当てるので、無闇に多様しないほうがいいんだなあと心で分かった。結構多様しがちだったので。例えば、Stringを生成してから&Stringで参照を得た場合のstrはStringのヒープ領域が参照先になっておるが、これをまたStringにし直したりと多重に生成してしまっていたことがあったが、Stringを一度参照したらなるべく参照し続けて使い回すのが良さそうだ。

Rustの配列とSliceの関係

配列はメモリ上の連続した領域に保存されている。Sliceはこのメモリの塊を表現するビューらしい。だから配列だけでなくVecにも使えるらしい。配列はコンパイル時に長さが決定しているが、Sliceは実行時に長さが決まるので配列の場合はこのように長さを指定しているが

let array: [&str; 2] = ["a", "b"];

Sliceの場合は長さがいらないようだ。このコードはPrimitive Type sliceのcoercing an array to a sliceのコードを参考に鋳造した。

let array: &[&str] = &["a", "b"];

でどうやら前から使っていたgetとかってのはsliceの機能らしいですな。Primitive Type arrayによると

Arrays coerce to slices ([T]), so a slice method may be called on an array.

配列はスライスに矯正される。したがってスライスメソッドが呼ばれる配列でとな。

Rustで配列の要素の書き換えと取り出し

まずこんな風にできる

let mut array = [1, 2];

assert_eq!(array[0], 1);
assert_eq!(array[1], 2);

array[0] += 1;
array[1] += 2;

assert_eq!(array[0], 2);
assert_eq!(array[1], 4);

ただこれだと存在しないインデックスにアクセスするとpanicになるのでgetメソッドを使ってOptionで取り出すのが良さそう

let mut array = [1, 2];

assert_eq!(array.get(0), Some(&1));
assert_eq!(array.get(1), Some(&2));
assert_eq!(array.get(2), None);

array[0] += 1;
array[1] += 2;

assert_eq!(array.get(0), Some(&2));
assert_eq!(array.get(1), Some(&4));

シェルスクリプトで配列をfor_each的に回す

bashで回したい。最近はシェルで限界まで処理するようにしている。

ARRAY=(a b c)

for VALUE in "${ARRAY[@]}"
do
  echo "$VALUE"
done

こんなふうな結果が得られます

$ /bin/bash hoge.sh 
a
b
c

なお手元の環境がzshだったりすると

$ sh hoge.sh
hoge.sh: 1: Syntax error: "(" unexpected

こんなことを言われるので注意が必要。

あと、ペアで回すような時はこんな感じでやることが多い。

#!/bin/bash

FROM=(
a
b
c
)

TO=(
foo
bar
baz
)

for INDEX in ${!FROM[@]}
do
  echo ${FROM[INDEX]} ${TO[INDEX]}
done

実行結果は下記のようになる

a foo
b bar
c baz

RustでTupleの要素の書き換えと取り出し

フィールド名で扱う

let mut tuple = (1, 2);

assert_eq!(tuple.0, 1);
assert_eq!(tuple.1, 2);

tuple.0 += 1;
tuple.1 += 2;

assert_eq!(tuple.0, 2);
assert_eq!(tuple.1, 4);

パターンマッチで扱う

let tuple = (1, 2);

let (mut a, mut b) = tuple;

assert_eq!(a, 1);
assert_eq!(b, 2);

a += 1;
b += 2;

assert_eq!(a, 2);
assert_eq!(b, 4);

なんとなくはめていたつもりだったが、これをパターンマッチと呼ぶのかと関心した。