昨晩、妻と「年上に見られがち」「若さってなんだろう」という中年のお手本のような会話を繰り広げた。
若さとは何かについては問われてみればいろいろな持論がでてきた、
- 一所懸命に何かに打ち込み続ける姿じゃないか
- ボロは着てても心はギャル、と云うことじゃないか
- 未来を悲観しないことじゃないか
など。
けれど最終的な着地としては「別に若く見られる必要ないよね、年齢不詳パーソンでいいじゃん」という感じになった。
昨晩、妻と「年上に見られがち」「若さってなんだろう」という中年のお手本のような会話を繰り広げた。
若さとは何かについては問われてみればいろいろな持論がでてきた、
など。
けれど最終的な着地としては「別に若く見られる必要ないよね、年齢不詳パーソンでいいじゃん」という感じになった。
先日の記事でも書いたとおり、最近 健康志向になってきてダイエットっぽいことをしています。
そんなわけで毎日体重計に乗ったりしてるわけですが。日々の体重データが蓄積されてくると「このデータを広く世界中の人が使えるように公開したいな」という思いが湧いてきました。
というわけで、三井の体重推移をリアルタイムで取得できる GraphQL API を作りました。
それがこれです。
クエリを叩くと 体重, 体脂肪率, 内臓脂肪, 骨量 などを取得できます。私が体重計に乗ってから6時間以内には反映されるはずなのでほぼリアルタイムです。
先日の記事では「バックエンドを Go にするか Rust にするか迷っている」と書きましたが。悩み抜いた結果私がたどり着いた結果は Deno でした。
正確に云うと Deno はランタイムなので、TypeScript 製ですね。
選定基準としては、
といったところです。
特に Deno Deploy は最近 Deno KV という Key-Value ストアをサービスとしてリリースしていて。これを使えばサーバーもデータベースも用意せず実行コードを書くだけで完結しそうだったので試してみました。
なんやかんやで Deno をちゃんと使ったのが初めてだったので、ここに感想を書き記しておきます。
バンドラーやフォーマッターの設定のことを考えなくていいので楽でした。
TypeScript のプロジェクト始めるとき毎回 Vite, Rollup, Webpack のうちどれ使うか迷って、設定の方法をググってたのが無くなってストレスフリーでした。
(余談ですけど、半年ぶりに Webpack の設定をググると同じことやろうとしてるのにプラグインとか設定の記述とかガラッと変えないといけなくてビビりませんか?あれ何?)
楽でした。
GitHub リポジトリと連携させておくと勝手に依存をインストールしてデプロイしてくれるので。
レイテンシや安定性については本気使いしていないのでよく分かりません。
とくに Deno KV はまだクローズドベータですしね。覚悟して来てる人だけが使うべきだと思います。
私は Deno が開発されているモチベーションや価値観に共感しているので、それありきで楽しんで使えたという感じでした。
つづいては可視化をやろうかなと考えています。
Astro でページ作って Deno Deploy に置く感じかな、たぶん。
2023-09-06 18:11:00 追記
この記事を公開した瞬間に Deno KV がクローズドベータからオープンベータになりました!
Deno Deploy と Deno KV を使ってみんなも軽率に個人情報を API にして公開しよう!!
私からは以上です。
Rust では所有権と借用, そしてライフタイムという概念があるため、値そのものを取り扱うより参照 (借用) を取り扱うことが難しくなりがちです。
特に関数から参照を返そうとすると、そこにはライフタイムの概念が絡んできます。
例えば下記のように関数内から文字列スライスを返す場合、
fn f() -> &str { "str" }
このコードはこのままではコンパイルできません。
コンパイラはこのようなメッセージを出してきます、
missing lifetime specifier. expected named lifetime parameter. help: consider using the `'static` lifetime 意訳: ライフタイム指定がありません。ライフタイムパラメータが必要です。 help: `'static` ライフタイムを検討してください
実際、コンパイラの言うとおり 'static
ライフタイムを明示すると先ほどのコードはコンパイル可能になります。
fn f() -> &'static str { "str" }
いったい何が起こっているのか、どんなシチュエーションでも 'static
さえ付ければ万事解決なのか、そのへんを解説していきます。
'static
ライフタイムとは何かRust のライフタイム注釈には特別な意味を持つキーワードがあります。
それが 'static
で全てのライフタイムのなかで最も長いものを表します。つまりはプログラムが起動しているあいだずっとということです。
先ほどの例で -> &str
ではコンパイルが通らず -> &'static str
ならコンパイルが通るのは、ライフタイムを 'static
とすることで「関数内の処理が終わっても値が drop されず有効であり続ける」ことを明示しているからでしょう。
ではどのような値が &'static
になれるのか、実際にコードを書いて試してみましょう。
&'static
になれる&'static
になれる&'static
になれない&'static
を得るテクニックがあるこの記事で示したサンプルコードは Rust Playground に置いています。
&'static
になれるまずはスカラー型から。
Rust には6種類のスカラー型があります。
i8
, i16
, i32
, i64
, i128
, isize
u8
, u16
, u32
, u64
, u128
, usize
f32
, f64
char
true
, false
()
これらは全て &'static
になれます。
サンプルコードを示すと、
fn i8() -> &'static i8 { &-1 } fn u8() -> &'static u8 { &42 } fn f64() -> &'static f64 { &std::f64::consts::E } fn char() -> &'static char { &'🦀' } fn bool() -> &'static bool { &true } fn unit() -> &'static () { &() } fn main() { println!("{:?}", i8()); // => -1 println!("{:?}", u8()); // => 42 println!("{:?}", f64()); // => 2.718281828459045 println!("{:?}", char()); // => '🦀' println!("{:?}", bool()); // => true println!("{:?}", unit()); // => () }
このように。
これらの型はどれも Sized で Copy でもあるので 'static ライフタイムとして取り扱えることはイメージに合うのではないでしょうか。
&'static
になれるタプル, スライス, 文字列スライス も (要素が &'static
になれる型であれば) &'static
になれます。
fn tuple() -> &'static (u8, char) { &(42, '🦀') } fn slice() -> &'static [char] { &['🦀'] } fn str() -> &'static str { "str" } fn main() { println!("{:?}", tuple()); // => (42, '🦀') println!("{:?}", slice()); // => ['🦀'] println!("{:?}", str()); // => "str" }
このことから分かるのは Sized ではない型でも &'static
になれるということです。
Sized であることは必要条件ではないんですね。
&[T]
や &str
でも &'static
になれない場合がある最終的な返り値が &[T]
や &str
であっても、それがヒープに置かれた値の参照だと &'static
になれないようです。
どういうことかと云うと、
// ↓↓↓ このようには書けない, &vec!['🦀'] のライフタイムは 'static ではない fn vec() -> &'static Vec<char> { &vec!['🦀'] // ^---------- // || // |temporary value created here // returns a reference to data owned by the current function } // ↓↓↓ このようには書けない, &String::from("str") のライフタイムは 'static ではない fn string() -> &'static String { &String::from("str") // ^------------------- // || // |temporary value created here // returns a reference to data owned by the current function }
上記の例はコンパイルが通りません。(エラーメッセージをコメントで併記しています)
vec!['🦀']
の型は Vec<char>
, &vec!['🦀']
の型は &[char]
です。
String::from("str")
の型は String
, &String::from("str")
の型は &str
です。
最終的に返されるのは &[char]
や &str
で、一つ前の例と同じです。関数のシグネチャも同じ。
ですがエラーが出てコンパイルできません。
Vec<T>
や String
の値を生成したとき、内部的にスライス (文字列スライス) が生成されてヒープに置かれます。
Vec<T>
や String
はヒープ上のスライスを指すポインタを所有していることになります。そして &
によって参照を得ると、ヒープ上にあるスライスの参照が返るわけです。
これが (おそらく) エラーの原因で。ヒープ上に置かれた値の参照は &'static
になれないようです。
-> &'static
付けとけば 'static
になれるわけじゃないってことですね。
&'static
になれる条件構造体について考えていきましょう。
もっともシンプルでオーソドックスな例として Option<T>
を取り上げます。一例として Option<u8>
は&'static
になれます。
fn some() -> &'static Option<u8> { &Some(42) } fn none() -> &'static Option<u8> { &None } fn main() { println!("{:?}", some()); // => Some(42) println!("{:?}", none()); // => None }
Option<T>
のような組み込みの型だけでなく、ユーザーが定義した構造体でも同じように &'static
になれます。
#[derive(Debug)] struct Foo<T>(T); fn struct() -> &'static Foo<u8> { &Foo(42) } fn main() { println!("{:?}", struct()); // => Foo(42) }
&'static
になれるVec<T>
, String
の例を見て「参照を内包していると &'static
になれないのかな?」と思いかけましたが、どうやらそうではないようです。
下記の例はコンパイルが通ります。
// &'static T を含む構造体 #[derive(Debug)] struct Bar<'a, T>(&'a T); // &'static な参照を内包する構造体 Bar の &'static な参照を返す fn struct_ref() -> &'static Bar<'static, u8> { &Bar(&42) } fn main() { println!("{:?}", struct_ref()); // => Bar(42) }
重要なのは参照している値が スタックに置かれているか/ヒープに置かれているか という点です。
&'static
になれない試しにBox<T>
を使ってヒープに置いた値を所有させるとコンパイルエラーを吐くようになります。
#[derive(Debug)] struct Baz<T>(Box<T>); // error[E0515]: cannot return reference to temporary value fn struct_box() -> &'static Baz<u8> { &Baz(Box::new(42)) // ^----------------- // || // |temporary value created here // returns a reference to data owned by the current function }
-> &'static
付けとけば 'static
になれるわけじゃないってことですね。(2回目)
スライス &[T]
や文字列スライス &str
が &'static
になれることからも分かるように、型が Sized である必要はありません。
?Sized な型を内包している構造体でも &'static
になれます。
// &'static T を含む構造体 // T が Sized でなくてもいい #[derive(Debug)] struct Qux<'a, T: ?Sized>(&'a T); // &'static な参照を内包する構造体 Qux の &'static な参照を返す fn struct_ref_unsized() -> &'static Qux<'static, str> { &Qux("str is unsized") } fn main() { println!("{:?}", struct_ref_unsized()); // => Qux("str is unsized") }
&'static
を得るここからはおまけです。
Vec<T>
から無理やり &'static [T]
を得る.leak()
メソッドを使うことで Vec<T>
から &[T]
を取ることができ、そのようにして得たスライスは &'static
になれます。
fn vec_leak() -> &'static [char] { // leak() メソッドを使って意図的にメモリリークさせると // Vec<char> から &'static [char] を取り出せる vec!['🦀'].leak() } fn main() { println!("{:?}", vec_leak()); // => ['🦀'] }
&'static str
を得るString
にも .leak()
メソッドがあります。それを使うと、
fn string_leak() -> &'static str { // leak() メソッドを使って意図的にメモリリークさせると // String から &'static str を取り出せる String::from("str").leak() } fn main() { println!("{:?}", string_leak()); // => "str" }
このように書けます。
メモリをリークすることさえも標準的な手段が用意されていて、ちゃんと型がついているのが面白いですね。
このあたりの思想や事情については「Rust 裏本」にある解説が興味深かったのでオススメです。
どんな値が &'static
になれるのか探っていきました。
体系的にまとまったレポートではないですが理解の助けになれば幸いです。
私からは以上です。
.leak()
メソッドを使ったテクニックについては @Toru31415926535 さん に教えてもらいました。
また X (旧: Twitter) で「ライフタイム何も分からん」と言ってるところに @nobkz さん から助言をもらい理解の助けになりました。
ありがとうございました。