Vecとスライス
計算物理では、時系列データ、粒子の状態、格子上の場など、多数の数値をまとめて扱います。1次元の数値データでは、まずVec<f64>とsliceを使えることが重要です。
所有するデータとしてのVec<f64>
Vec<f64>は、実行時に長さが決まる数値列を所有します。例えば、時刻ごとのエネルギーを保存する場合は次のように使います。
let mut energy: Vec<f64> = Vec::with_capacity(1000);
for step in 0..1000 {
let e = step as f64 * 0.01;
energy.push(e);
}
要素数の上限が分かっている場合は、Vec::with_capacityで容量を確保しておくと、途中の再割り当てを減らせます。
関数境界としての&[f64]
関数に数値列を渡すときは、多くの場合Vec<f64>そのものではなく&[f64]を受け取ります。
fn mean(xs: &[f64]) -> Option<f64> {
if xs.is_empty() {
return None;
}
let sum: f64 = xs.iter().sum();
Some(sum / xs.len() as f64)
}
&[f64]はデータを所有しません。配列、Vec<f64>、配列の一部などを同じ関数に渡せます。
let xs = vec![1.0, 2.0, 3.0];
let avg = mean(&xs);
let first_two = mean(&xs[0..2]);
更新を伴う関数境界としての&mut [f64]
データをその場で更新する関数では、&mut [f64]を使います。
fn scale(xs: &mut [f64], factor: f64) {
for x in xs {
*x *= factor;
}
}
let mut xs = vec![1.0, 2.0, 3.0];
scale(&mut xs, 0.5);
可変にする範囲を関数境界で明示することで、どのデータが変更されうるかを読み取りやすくなります。これはAI agent が生成した差分をレビューするときにも役立ちます。
stack、heap、所有権
小さな固定長配列、例えば3次元ベクトル[f64; 3]は値そのものとして扱いやすいデータです。一方、Vec<f64>は、長さが実行時に決まるデータをheap上に持ちます。
数値計算コードでは、次の使い分けを基本にします。
- 小さく固定長の量:
[f64; 2],[f64; 3] - 実行時に長さが決まる1次元データ:
Vec<f64> - 関数の入力:
&[f64] - 関数内で更新する入力:
&mut [f64]
所有権や借用は、最初は複雑に見えるかもしれません。しかし、数値計算では「どの関数がデータを所有するか」「どの関数が読むだけか」「どの関数が更新するか」をはっきりさせる仕組みとして有用です。