RustにいればRustに従え(10) 2次元配列を乱数で初期化したいです。mut有for有

Joseph Halfmoon

今回はmut無for無でないです。配列を乱数で初期化したかっただけなのですが。それどころかRustではご縁がなかった「Stack Overflow」(有名なサイトではありませんよ)に遭遇。あれれ、ヒープに配列とれば済むというものでもないのね。それに外部クレートの使い方のお作法、カッコいいけど、知らないとなんだかなあ。

※『RustにいればRustに従え』関係記事 index はこちら

※動作確認は、Windows11のWSL2上にインストールしたUbuntu20.04LTS上のrustc 1.64.0 (a55dd71d5 2022-09-19) で行っています。

ちょいと大き目の配列を確保しようとしたら

年寄なので、大昔64Kバイトのセグメントというものの支配下におった時期があるのです。ちょっと気を緩めるとコードもデータも溢れてました。その点最近は気楽で良いなと思いつつ、ついついその辺を突いてみたくなるのであります。

今回、試しに以下のような1次元配列を確保しようとしました。

let test_array: [i32; 1500*1500] = [0; 1500*1500];

すると、エラーが。RustStackOverFlow

Rustの場合、キホン、配列もStackの上に確保されるようです。i32型(4バイト)を1500×1500個=225万要素、4×225万で9Mバイト(10進)確保しようとしたらコケているみたい。

ヒープからメモリ切り出したら良いんでしょ、と書き換えてみました。

let test_array: Box<[i32; 1500*1500]> = Box::new([0; 1500*1500]);

でもね、ヒープ使っているハズのBoxでもstack overflowが起きます。なぜに?その辺の事情を調べた方がおられました。以下は大変為になります。ありがとうございます。

Rustで大きな構造体を確保する際のスタックオーバーフローを回避する方法

Boxでも初期値を生成するときにスタックを使ってしまうのね。。。トホホ。上記ページをみると解決策はいろいろ書かれてます。フツーでないコンパイラを使ったり、unsafeなポインタを使ったり、スタックサイズを広げたりと結構過激っす。やれば出来るっと。まあ今回はそれほどサイズにこだわっているわけでないので、コンパイルが通るサイズで「許してやるか」。

ちなみに以下のサイズではコンパイルが通りました。

let test_array: [i32; 1400*1400] = [0; 1400*1400];

1400x1400x4 = 7840000バイトはOKだと。こんだけあれば普通は足りるか。普通って何よ?

乱数を使いて~、randトレイト?

そういう分けで、配列領域を確保してからおもむろに乱数を詰めることになりました。アカラサマな書き換えが必要なので、配列は mut 必要ね。さてCで乱数を使うときは、ちょいとヘッダファイルをインクルードしてやれば良い(あまり素性の良い乱数ではないみたい)ですが、Rustの場合はそう簡単でもないです(初回の1回だけだけれども。)

いつもお世話になっております「The Rust Programming Language 日本語版」様の

数当てゲームのプログラミング

を読むとよくわかるのですが、「randクレート」という名の「外部クレート」を使うと乱数を生成できるみたいですが、初期状態では「randクレート」はインストールされていないみたいっす。でも、超カッコイイ cargo システムはほぼほぼ自動でrandクレートのインストール作業をやってくれます。ただし、Cargo.tomlファイルの中の[dependencies]の下に以下を書き入れてあるならば。

rand = "0.8.3"

ライブラリ・バージョンの違いでトラブったことがある人であれば立派な見識?と讃える機能かと。なお、上記は上の「数当てゲームのプログラミング」で使っているバージョンです。下をみると当方環境に自動インストールされたのは0.8.5でした。どうも0.9.xみたいなバージョンには自動で上げてくれないらしいので、もっと新しいのがあるかも知れんです。

上記を設定後、ソースファイルに以下書いておけば、

use rand::Rng;

そのソースのビルド時に依存関係のある者どもが自動取得されて、コンパイル、インストールされていきます。こんな感じ。randInstall

cargo カッコええけど、使い方がいろいろありすぎてムツカシー。

実験

以下の短いコードで、スタック上にとった100×100サイズの2次元配列を乱数で初期化してみました。

use rand::Rng;

fn main() {
    println!("random_array");
    let mut test2d_array: [[i32; 100]; 100] = [[0; 100]; 100];
    for i in 0..100 {
        for j in 0..100 {
            test2d_array[i][j] = rand::thread_rng().gen_range(0..256);
        }
    }
    println!("{}",test2d_array[0][0]);
    println!("{}",test2d_array[1][1]);
    println!("{}",test2d_array[99][99]);
}

結果が以下に。randomArrayResult

mut 有り、for 有り だとフツ~のプログラミング言語みたいだな。当たり前か。

RustにいればRustに従え(9) 方程式の求解、Newton法テキトー版mut無for無 へ戻る

RustにいればRustに従え(11) 行列積を求めたいです。普通な感じで。mut有for有 へ進む