ことの発端
最近、Rustに入門しました。
どんな言語も課題があると習得も早いだろうってことで、まずはReactiveXをRustで実装してみました。1
ReactiveXは関数型や非同期を扱うライブラリで、ほぼすべてがジェネリクスで構成されます。
その中で、なかなかスンナリ入ってこなかったのが static でした。
で、個人的に色々とやってみて、「なるほど」となったので記事にしてみました。
Rust の static
まず、C++のstaticとは似て非なるものです。
罪悪感を抱えながら「今日もstatic(グローバルステート)を増やしてしまった。。。」的な「あのstatic」ではありません。
まず、Rust の static には2種類あります。
-
- staticライフタイム
- staticライフタイム境界
staticライフタイムは C++ の static と同じです。
が、しかし、Rustではプリミティブ型やリテラルだけが static 定義できます。つまり、オブジェクトは static ライフタイムが使えません。Singletonパターンを使ってグローバルステートを実装することはできますが、staticライフタイムなオブジェクトは作れません。
ほいで、次の staticライフタイム境界 が曲者です。
staticライフタイム境界 とは
ざっくりいうと、
「 参 照 が 含 ま れ な い も の 」
です。
参照は所有者が居てその所有者が手放すまでが生存期間(ライフタイム)になりますが、その参照が含まれていなければ全てstaticライフタイム境界です。(もうね〜、何というか、言い方変えて欲しい。。。)
具体的にコードで実証してみます。
staticライフタイム境界 を感じる
準備
まず、こんなコードを書きます。
fn is_static<T>(_: T)
where
T: 'static, // <-- これが 「satticライフタイム境界」 というヤツです。
{}
これはstaticライフタイム境界の変数を渡さないとコンパイルエラーになる関数です。
この関数に色々と値を渡してみると理解が早いかもです。
リテラル
C++erは、この時点で「??」ですが、これが先述のstaticライフタイム境界です。
is_static(123);
実体をmove
let x = 0;
is_static(x);
参照
「参照が含まれるもの」というか、参照そのものもNGです。
let x = 0;
is_static(&x);
クロージャー(外部変数のキャプチャなし)
C++ ではラムダ式です。
let f = || {};
is_static(f);
クロージャー(参照でキャプチャ)
クロージャーは外部変数のキャプチャしているオブジェクトです。
このキャプチャしている変数は move を指定しないと参照になります。
let x = 0;
let f = || println!("{}", x);
is_static(f);
クロージャー(moveでキャプチャ)
move すればクロージャー内は実体を所有することになるので、無問題。
let x = 0;
let f = move || println!("{}", x);
is_static(f);
‘static str&
staticライフタイムの参照は許されます。
let a = "abc"; // <-- &'static str
is_static(a);
所感
やっぱり static と書くのは抵抗がある。。w