Location via proxy:   
[Report a bug]   [Manage cookies]                

2024年の思い出

しゃかいじんじゅういちねんめ

去年は https://eagletmt.hateblo.jp/entry/2023/12/31/194801

仕事

One Experience というちょうど一年くらいかかった一大プロジェクトが今年の9月頃まで続いていた。去年イギリスに滞在する機会があったのはこれが理由だった。自分は異なるシステム間で単方向のデータ移行というものを担当していて、技術的な概要は社の開発者ブログに書いた通りだが、まぁ大変な話であった。レシピのデータ構造は両者似たようなもので比較的移行しやすかったが、たとえばユーザのデータ構造はかなり離れており、データ元のデータ更新をどう移行するかを考えて実装するのは大変だった。またユーザのデータはメールアドレスやログイン資格などのセンシティブなデータも多く、その意味でも扱いが大変だった。

One Experience プロジェクトに一区切りついてユーザから見たサイト、モバイルアプリは1つになったし Web アプリのコードベースも統合されたが、我々が提供しているサービスはその1つではなく他にもあり、全社で見ると依然として日本のチームが運用してきたものとイギリスのチームが運用してきたものの2つのプラットフォームが存在している。人員もサービスも縮小したいま2つのプラットフォームがあるのは運用上の負荷しかないので、これを1つに統一するということに少しずつ取り組み始めているところである。またプラットフォームだけでなくチームもまた1つに統一していく必要があるわけだけど、日本のチームとイギリスのチームでは言語の壁も時差の壁も厚く、どううまくやっていくか今後模索していくことになる。

イギリスのチームが運用していたプラットフォームでは EKS が採用されていたので、仕事で Kubernetes について触れる機会が一気に増えた。まぁとにかく複雑という感想で、Kubernetes のオブジェクトとコントローラという概念ですべてのものを再構築していて、その抽象化によってオンプレからクラウドまでどこでも運用できるようになっているんだろうけど、数十人規模の開発者が AWS でいくつかの Web アプリを動かすだけの状況では不必要な抽象化レイヤーが多すぎるように感じる。

那覇で開催された RubyKaigi に久しぶりに行った。それまで2016年の京都での RubyKaigi が最後だったはずなのでだいぶ間が空いている。今年参加したのは単純に那覇に行ってみたかったというのもあったし、かつての同僚たちと再会したり最近あまり追えてなかった Ruby 界隈 (?) の状況を感じたいという理由だった。ただ、One Experience に関するアレコレが会期中にも発生して、会にそこまで集中できなかったのは残念だった。

趣味

メトロイドヴァニアというゲームジャンルが好きなことが分かってきたので、今年発売されたゲームを結構やってみていた。その中からおすすめできるものを以下に並べてみる。上にあるほどよりおすすめできる。

  • 九日ナインソール https://store.steampowered.com/app/1809540/_/
    • パリィ要素がやや強めのソウルライク。自分はパリィはあんまり得意ではないが、タイミングが少しズレても多少デメリットを受けながらもパリィ自体には成功するという調整がちょうどよかった
    • 美術、音楽、物語も良く、かなり没頭して遊ぶことができた
    • ラスボスを倒すのに4時間くらいリトライし続けたが、リトライが苦にならないような親切さがあるし、リトライを続けていくうちに徐々に上達していく気持ち良さがあった
  • プリンスオブペルシャ 失われた王冠 https://store.steampowered.com/app/2751000/_/
    • 一風変わったアクションもありつつも、わりと王道なメトロイドヴァニア
    • 難易度は中程度でボリュームがあり、何度もリトライする前提の作りではない中では良作だと思う
    • Ubisoft Connect をインストールさせられ Ubisoft アカウントが必要なのが玉に瑕……
  • バイオモーフ https://store.steampowered.com/app/1430220/_/
    • 倒した敵に変身 (バイオモーフ) して敵の能力を使いながらマップを攻略するゲーム
    • 難易度はプリンスオブペルシャ 失われた王冠と同じくらいで、高難易度というほどではなくそこそこというかんじ
    • 敵への変身が独自要素ではあるが、変身すると基本的に移動しづらいのでマップ上の特定のギミックを突破するためだけに使って、それ以外は基本フォームでのアクションになってしまうのがもったいない気がする
  • ANIMAL WELL https://store.steampowered.com/app/813230/ANIMAL_WELL/
    • 攻撃手段が無く、アクションゲーというよりパズルゲーのほうが近いかもしれない
    • マップを踏破しながら行動範囲を広げていくという意味ではメトロイドヴァニアと言えそうで、その濃度が高い作品だった
  • ボウと月夜の碧い花 https://store.steampowered.com/app/1614440/_/
    • 空中で攻撃を当てると再度ジャンプできるというシステムが独特で、これを活かしたギミックがたびたび登場する。必然的に滞空時間が長くなり緊張感がある
    • 全体的に良作ではあるものの、ワイヤーアクションの使い勝手が自分には馴染まず、そこが微妙だった

来年は1月からエンダーマグノリアが出るのが楽しみ。

今年も色々なライブに行った。全プリキュア 20th Anniversary LIVE!、イロドリミドリLIVE’24、HIMEHINA LIVE 2024、Nornis LIVE TOUR 2024、i☆Ris 12th Anniversary Live、amusement music fes、あかりGeneration 10周年記念、など。i☆Ris の周年ライブはぴあアリーナ MM での開催で、週一でチケット販売状況を更新していてすごかった https://iris.dive2ent.com/12th-anniversarylive/ 。あかジェネ10周年は同窓会感があってよかった。最近はそこまで頻繁にアイカツ曲を聞いてるわけではないけど体が覚えていた。Let's アイカツ! もやってほしかったねぇ。

(追記) 今年は Palette Project (パレプロ) を知った年だった。きっかけはオンゲキにコラボで3曲収録されたことで、どれも良かったから調べてみたらバーチャルアイドルユニットであることを知った。他の曲も聞いてみても良さそうだったのと、過去のライブのダイジェスト映像的なものを見たらライブでより良さがあるようなものに感じたので、突発で感謝祭2024のライブに行って実際にかなり良かった。パレプロをもっと早く見つけたかったという後悔もありつつ、きっかけを与えてくれたオンゲキにも感謝。

ソシャゲや音ゲーはあまり変わらず。スタレのピノコニー編がかなり面白く、一番更新が楽しみなゲームだった。その流れで崩壊 3rd のシナリオも読みたくなり、スタレコラボイベントのタイミングで始めた。ちょびちょびと進めていてシナリオは面白いところまでまだ進められてないけど……

今年の2月にクロスバイクを買った。いまの住居に引っ越してからはママチャリ*1を買い直して時々使っていたけど、徐々に行動範囲が広がっていってもうちょっといい自転車が欲しいなと思えてきたのでクロスバイクにした。クロスバイクを使うのは人生初で最初の数週間くらいは慣れずに体力を無駄に消費していたが、慣れてきてからは実際乗りやすいなと感じるようになった。そして、片道1時間くらいの範囲であれば基本自転車で移動するようになった。また、オフィスが再び都内に移転してきて、たまたま自宅から自転車で行きやすい場所だったので、通勤も自転車にしている。夏はちょっと早めに出勤して日が落ちてから退勤すれば熱中症対策はそこまで必要ではなく、上の着替えや汗拭きシート的なものを用意しておく程度で済ませられる。一方これからの時期の寒さは悩みどころで、最近は午前中に自宅で仕事した後に出勤するようにしていて、退勤時は日光がない分寒く感じるが気温はそこまで下がらないので何とかなっている。

*1:最近? はシティサイクルと言うらしい

2023年の思い出

しゃかいじんじゅうねんめ

去年は https://eagletmt.hateblo.jp/entry/2022/12/31/165730

仕事

今年は会社の状況が大きく変わった年だった。退職勧奨やレイオフ、その変化による自主的な退職でとにかく人が減った。それによって自分の仕事にも当然変化があり、今年の後半はイギリスにいるメンバーとコミュニケーションをとる機会が増えた。その関係で一週間ほどイギリスに滞在する機会もあった。海外に行くのは2019年のシアトル以来で、シアトルもなかなか遠かったがイギリスは更に遠い。イギリスは楽しめたが14時間のフライトは拷問以外の何物でもない。

イギリスにいるメンバーとコミュニケーションをとる機会が増えたということは英語でコミュニケーションをとる機会が増えたということで、これが自分にとっては大変だった。Slack や GitHub で英語話者とコミュニケーションをとる機会は今までもあったが、頻度が増えた上に英語の長文 issue やドキュメントを読むのがとにかく遅いのが困る。ソフトウェアエンジニアだと日本人でも AWS コンソールを英語で使ったりスマートフォンを英語で使ったりしている人は普通によく見るが、自分は公式の日本語版がある場合は基本的に日本語版を利用している*1。自分は長い文章については一度全体を流し読みしてから重要そうな箇所を精読し直すような読み方を日本語ではよくしていて、それを英語でできるだけの力がないので日本語と比較して読むのにすごく時間がかかってしまう。英語の力を高めたい気持ちが無いわけではないが、英語メインで仕事をしたいかというとそれは全く無いので、モチベーションが難しい。仕事で必要だから JavaPython に詳しくなりたいけど、別に積極的に JavaPython を使いたいわけでは全くないよな…… みたいなところに英語もある。いや JavaPython よりはもうちょっとモチベーションは高いか。

趣味

ブルーアーカイブは引き続き継続できている。今年の頭には最終編という大きなコンテンツがあって、世間的 (?) にもめちゃくちゃ盛り上がってたと思う。一方で盛り上がりすぎて、一部の変なファンが目立ったりリアルイベントに人が多すぎたりで、数年前のホロライブみたいに急速に自分の興味を失う不安もある。まぁゲーム本編がちゃんと面白いのでゲームをやらなくなる心配はしていない。

あとは崩壊スターレイル (スタレ)、ワールドダイスター夢のステラリウム (ユメステ) と日常的にやるソシャゲが増えた。どちらもリリース初日でこそないがリリースから近いうちから始めた。スタレは PC でプレイできて画面がとても綺麗なのと、原神よりも攻略がだるくないのが気に入っている。対人でランキングを競う要素が無いのも気楽でよい。ユメステはスマホリズムゲーに欲しいものが全部詰まっているのが良い。最高難易度帯になると突然3本以上の指を使う必要がでてくるがずっと2本指だけでやってきた人が3本以上に慣れるためのほどよい難易度の譜面が無い、というのがスマホリズムゲーあるあるだと思うが、ユメステは3本以上必要な譜面を OLIVIER という別の難易度に隔離して、その中で低難易度から高難易度までレベル分けしてあるのがえらい。ただ、この特徴もプロセカ3周年で APPEND という形で実装されてしまい、独自性が早くも失われてしまったので、ちょっと今後が心配ですが…… ユメステはライブイベントも今年豊洲 PIT で行っていて、自分も行ったけどカバー曲無しでオリジナル曲だけという内容で、ゲームリリースから半年経たずにキャストが全員いるわけでもないのにそれができるのは結構すごいなーと思った。

あとはアイカツ10周年の映画とミュージックフェスタ FINAL も今年だった。映画はスタッフトーク回にも行って監督、脚本、プロデューサーのサインの入ったポスターが当たったのが本当に嬉しかった。今も部屋の目立つ位置に飾っている。

FINAL 1日目の冒頭に音響トラブルでダイヤモンドハッピーが中断されておたく大合唱になったり長い MC になったりしたのも良い思い出です。つい先日にフレンズ5周年のイベントがあったり Resound Stars のリベンジ公演があったように、アイカツシリーズの新作が止まってしまっても周年イベントは今後もあってほしいなぁ。5年刻みでも無印 (2012)、スターズ (2016)、フレンズ (2018)、オンパレード (2019)、プラネット (2020) で綺麗に毎年できるね。

他に今年行ったライブは Nornis 1st、i☆Ris 8th、Mia REGINA HEARTBEAT AGAIN、HIMEHINA LIVE 2023 あたりか。後半になるにつれてマスク無しで普通に声を出せるようになってよかった。Nornis は会社のチームメイトも行ってて面白かった。自分は主に戌亥、彼は主に町田目当てだったので今も穏やかな関係が続いています。Mia REGINA はラストライブということで、アイカツ共々今年に閉じたんだなという印象になった。どちらの最終ライブもとても楽しかった。HIMEHINA は今回も新たに別の友人を連れて行って布教した。安定感があるし万人に勧められるというかんじなので次のライブが早く決まってほし~。

音ゲー関連は実力的にもあんまり変わらず。CHUNITHM は今も頻繁に遊んでるが、オンゲキのほうは最寄りのゲーセンから消えたこともあってちょっと足が遠のいてしまっている。今年は自転車をよく使うようになったのでオンゲキのあるゲーセンに行くことも頻繁にあるんだけど、今の時期のように寒いとあまり自転車にも乗りたくないし、それとオンゲキに使う時間がスタレやユメステに変わってしまった感が無くはない。

今年最もプレイしたゲームはティアキンだった。一番面白かったのもティアキンと言っていいだろう。あとは先月末に出たばかりのゲームだけど TEVI https://store.steampowered.com/app/2230650/TEVI/ がとても面白かった。いわゆるメトロイドヴァニアと呼ばれるような 2D 探索アクション RPG をベースに、弾幕ゲーっぽい要素 (敵の攻撃が基本的に弾で、主人公の当たり判定が小さく、ボムで相手の弾を消せる) や格ゲーっぽい要素 (コマンド入力で攻撃) を足した盛り盛りなゲームになっている。自分は格ゲーに馴染みが無いのでコマンド入力せず基本的な攻撃のみで進めたけど。Steam のインディーゲーとしてはちょっと高めの値段だけど、その分ゲームボリュームはあるしサウンドも良いし日本語の声優がやたら豪華だったりする。

趣味開発は…… 今年はこれといったものは無いかなぁ。ISUCON13 で移植のお手伝いをしたくらいか。今年は Ruby と Rust の2つで移植を担当して、Rust のほうは去年までの Actix ではなく Axum を採用するというトライをした。自分が仕事や趣味で使うのもほぼ Axum 一択になってきている。Ruby にも何かしらのトライを入れたかったけど、Trilogy を使ってみるには mysql2-cs-bind 的な gem が無いのが苦しそうだったのと、RBS 対応をやってみようとしたけど Sinatra だと苦しそうだったので、ほぼ例年通りというかんじになってしまった。

*1:ただし AWS のドキュメントは除く

2022年の思い出

しゃかいじんきゅうねんめ

去年は https://eagletmt.hateblo.jp/entry/2021/12/30/164456

仕事

2022年が始まったときは自分で手を動かして新しいことに取り組みたいなと意気込んでいたが、2月にデータ基盤チームが解散となり、DWH をはじめとするデータ基盤まわりの仕事を引き継ぐことになり一年を通してこれが自分の仕事の中心となった。解散前にちゃんと引き継ぎのフェーズがあった*1し、DWH 自体は元々興味のある領域だったので DWH の仕事は嫌では全くなかったが、とはいえコードの多くが初見であり社内の標準的な技術スタックから外れたシステムが多かったので、問い合わせ対応やトラブル対応に時間がかかる日々がしばらく続いた。主な仕事としてはログデータに紛れ込んでしまった個人情報をマスキングするしくみを作ったり、Prism が利用している PostgreSQL のデータを削減したりしていた。

これ以外の新しいこととして、就業型インターンシップで学生を多く受け入れた。最近知ったことだが、チーム単位で就業型インターンシップを受け入れた数を比べると自分のチームが特別に多かったようだ。自分のチームには去年から就業型インターンシップを継続してくれている方もいる中で新規に受け入れようと思ったのは、いわゆるリファラルだけでなく現時点で全く繋がりがない人にもきてほしいという個人的な思いがあった。ただ、インターンシップの期間が短すぎたなという反省点がある。自分のチームではたくさんの小さなシステムを開発・運用しているためオンボーディングのコストが高く、最初に目に見える成果を作れるまでの時間がどうしてもかかってしまう。一方で短い期間内に作れそうなものとなるとこぢんまりとしたタスクになってしまう。もし今年うちのチームに就業型インターンシップで来た人がこれを読んでいたら、この点は自分の見積もりが甘くてすまなかったと思っていることを伝えたい。来年も受け入れは続けるが、おそらく今よりも長期間を前提にした募集にすると思う。

趣味

今年はブルーアーカイブへの熱が高まった年だった。去年の8月か9月くらいに始めて、今も継続している。そもそもリズムゲー以外のソシャゲをほぼやったことないというのはあるけど、ここまでソシャゲが継続するのは人生初。当時からシナリオの評判が高かったけど個人的には最初はそこまでピンときてなくて、いま最も評価が高いだろうエデン条約編のシナリオが結構進んだあたりでようやく面白いなと思えた。全体的に昔のエロゲを思い出すようなノリで、そのへんが好きだった世代に刺さりやすいんだと思う。自分の好きなイラストレーターもだいたいブルアカの二次創作をしてるし。ソシャゲ関連だと他にヘブンバーンズレッド、INFINITY SOULS をやった。ヘブバンは麻枝准にはずっと曲とゲームシナリオを書いていてほしいという気持ちが高まった。麻枝准のシナリオが好きならヘブバンのメインストーリーを楽しめると思うけど、ブルアカ以上にメインシナリオを読むための戦闘がキツいのが微妙…… Steam 版を出したのはまじでえらい。リズムゲー以外のあらゆるソシャゲは Steam 版を出してほしい。INFINITY SOULS はたしか2017年のコミケでチラシを配っていたものがようやくリリースされて遊んでみたけど、正直に言ってゲームとしてはだいぶつまらなかった。自分は都築真紀のファンなのでシナリオやキャラクターには惹かれるところがあったけど、戦闘やゲーム性はキツい。開始3ヵ月でテコ入れのようになのはがメインストーリーに絡み始めたのは驚いたが、そこから2ヵ月でサービス終了となり、半年ももたずに消えていってしまった。

今年やってとくに印象に残っているゲームは星のカービィディスカバリー、十三機兵防衛圏、ENDER LILIES かなぁ。例年よりちょっと少ないかも。ポケモン LEGENDS アルセウスはそこまで好きにはなれなかった。ポケモン SV についてはストーリーはポケモンとは思えないくらいしっかりとしていたし戦闘面での調整も基本的に良いものだと思うけど、イベントやマップによっては処理落ちがひどかったり、必要なテラピースの数が多すぎたり、ボックスの表示が遅すぎたりと、なかなか微妙な点が多かった。ポケモン SM や USUM のときもそうだったけど、現代のコンソールゲームにおいて通常プレイで処理落ちがする品質のゲームってちょっとな…… カービィディスカバリーは 3D 初とは思えないくらい違和感ない操作性や視認性があってよかった。ちゃんとクリア率100%までやった。十三機兵防衛圏はこれもまた戦闘パートが蛇足に感じたけどストーリーやキャラクターが良い作品だった。ENDER LILIES はつい最近やったゲームだけど、いわゆるメトロイドヴァニアでインディーとは思えないクオリティで楽しかった。

音ゲー関連では CHUNITHM はレートが MAX 16.16 -> 16.37、バトルランクが S3 -> SS2 になった。レートに対してバトルランクは高いほうだと思う。そして XL TECHNO -More Dance Remix- で無事に S ランクをとり、今年追加されたレベル15でもすべて S ランクをとり銀ポゼになった。次の区切りとしては金ポゼは遠いのでレート 16.50 かなぁ。オンゲキのほうははちゃめちゃに高難易度が追加されていって、解禁がとにかくダルいのがつらかった。オンゲキって今まで楽曲の解禁にノルマ的なものがなくてすごく楽なのが良かったのに…… コンテンツの終了宣言的なものがされ、その具体的な意味が今も分からないままで来年どうなってるんですかね。

今年の Q3 (って表現であってるのか?) は久しぶりにテレビアニメを見た。これも都築真紀の新作である Extreme Hearts が主目的で、ついでにリコリス・リコイル等も見ていた。Extreme Hearts はたいへん面白かったです。

ヒメヒナの現地ライブ良かったですね。前回、前々回のオンラインライブの再演ではあるけど、現地には現地の良さがある。あとは声を出せればというかんじだけど、この点についても最大限気を遣ってハミングなら OK というルールが設定されていたのも良かった。Studio LaRa になったのもこのときだったね。次のライブが来年8月5日と発表されていて、まだ少し先だけど楽しみ。

アイカツがついに10周年となり、記念の映画が公開されたり4ヵ月連続ライブが始まったりした。じわじわと新曲も出てきて良い。その一方でコンテンツとしては止まってしまっているので、来月の映画公開と2月の 2 days のミュージックフェスタ FINAL で終了なのかなぁ。

最後に趣味開発について触れると、slack-thread-expander くらいかな。あとは json-to-parquet も書いたりしたけど、どちらも仕事で欲しくなって書いたのであんまり趣味感がない。slack-thread-expander は Slack のスレッドが大好きな人たちがついスレッドを使ってしまったときにスレッドを無意味にするために書いて、社の Slack で使えるようにしてどうしてもスレッドを使ってほしくない特定のチャンネルのみで有効化したんだけど、自分が知らないチャンネルにも勝手に導入されていて (誰でもどこでも使えるようにしたのでこれ自体は全く構わない)、そこでは普通にスレッドがバンバン使われて slack-thread-expander が日常的に投稿していて、自分の意図とは異なる目的で利用されているのを目撃したりした。そこでは何のために slack-thread-expander が導入されていたのかは僕は分かりません。

*1:明確な引き継ぎがあるのは自分の経験上かなり希である

MySQL でも楽に bulk insert したい

以前 PostgreSQL で unnest を使って楽に bulk insert する方法を紹介した https://eagletmt.hateblo.jp/entry/2021/09/01/030453 。 これの MySQL 版が欲しかったものの array 型が存在しない MySQL では無理かなと思っていたんだけど、MySQL 8.0 からサポートされた JSON 型なら array を表現できて json_table() という関数を使うと達成できそうなことにふと気付いた。

json_table() を使うと JSON の値からテーブル (行) に変換することができる。つまりこれを使えば unnest に近いことが可能になる。
https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html

MySQL [(none)]> select * from json_table('[{"x": 1, "y": 2}, {"x": 3, "y": 4}]', '$[*]' columns (x integer path '$.x', y integer path '$.y')) as t;
+------+------+
| x    | y    |
+------+------+
|    1 |    2 |
|    3 |    4 |
+------+------+
2 rows in set (0.001 sec)

文法がなかなか独特だけど1つの JSON の値 (配列) から行に変換できている。なのでプログラム側は JSONシリアライズさえできれば insert into select で bulk insert が可能になる。 PostgreSQL 版と同様に Rust の sqlx を使った例はこんなかんじになる。

#[derive(Debug, serde::Serialize)]
struct Record {
    x: i32,
    y: i32,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let records: Vec<_> = (0..1000)
        .map(|i| Record {
            x: 2 * i,
            y: 2 * i + 1,
        })
        .collect();

    let pool = sqlx::MySqlPool::connect("mysql://...").await?;

    let query = "insert into bulk_inserts (x, y) select * from json_table(?, '$[*]' columns (x integer path '$.x', y integer path '$.y')) as t";
    sqlx::query(query)
        .bind(sqlx::types::Json(records))
        .execute(&pool)
        .await?;
    Ok(())
}

がんばってプレースホルダを組み立てる必要がなくなって便利。

sqlx crate でフィールド毎に独自のデコード処理を挟みたい

sqlx-with というものを作ってみたので、その経緯について書く。

github.com

sqlx crate でフィールド毎に独自のデコード処理を挟みたい

sqlx では query_as() 等の関数を使うことでデータベースから取り出した行を struct にマッピングすることができ、このマッピングsqlx::FromRow という derive macro を利用することで自動で実装できる。

#[derive(sqlx::FromRow)]
struct Row {
    x: i64,
}

このような単純なマッピングではなく、特定のカラムの値に独自の変換処理を入れてから struct に入れたいとする。とくに意味は無いが、たとえばカラム x の値に対して (x, x + 2) という値を struct のフィールド x に入れたいとする。serde であればたとえば split_x という関数を定義して #[serde(deserialize_with = "split_x")] と指定すれば実現できる。これと同じような機能が sqlx::FromRow にあればよさそうだが、現状は無い。なので serde の deserialize_with みたいなものをどうやったら実装できるか考えてみる。最終的には以下のように書けるとよさそうだ。

#[derive(sqlx::FromRow)]
struct Row {
    #[sqlx(decode = "split_x")]
    x: (i64, i64),
}

まず sqlx::FromRow が具体的にどのようなマクロ展開をしているのか実装を見てみると、Row、ColumnIndex、Type、Decode といった様々な trait を使っていることが分かる。
https://github.com/launchbadge/sqlx/blob/v0.6.1/sqlx-macros/src/derives/row.rs
最初の例は以下のような impl を生成している。

struct Row {
    x: i64,
}
impl<'r, R> sqlx::FromRow<'r, R> for Row
where
    R: sqlx::Row,
    &'r str: sqlx::ColumnIndex<R>,
    i64: sqlx::Type<R::Database> + sqlx::Decode<'r, R::Database>,
{
    fn from_row(row: &'r R) -> sqlx::Result<Self> {
        Ok(Self {
            x: row.try_get("x")?,
        })
    }
}

特定のデータベースに依存しないようになっているのが特徴で、struct のフィールド名とその型の情報から

  • &str で特定のカラムにアクセスできる
  • i64 に対応するデータベース側の型がありデコードができる

という制約をつけている。

ここに split_x という関数を挟んでみると以下のような実装になるだろう。

struct Row {
    x: (i64, i64),
}
impl<'r, R> sqlx::FromRow<'r, R> for Row
where
    R: sqlx::Row,
    &'r str: sqlx::ColumnIndex<R>,
    i64: sqlx::Type<R::Database> + sqlx::Decode<'r, R::Database>,
{
    fn from_row(row: &'r R) -> sqlx::Result<Self> {
        Ok(Self {
            x: split_x("x", row)?,
        })
    }
}

fn split_x<'r, R>(index: &'r str, row: &'r R) -> sqlx::Result<(i64, i64)>
where
    R: sqlx::Row,
    &'r str: sqlx::ColumnIndex<R>,
    i64: sqlx::Type<R::Database> + sqlx::Decode<'r, R::Database>,
{
    let n: i64 = row.try_get(index)?;
    Ok((n, n + 2))
}

あとはこのように展開されるようなマクロを作るだけ…… とはいかず、このままだと無理なことが分かる。Row の定義から impl の制約を生成しなければならないが、i64 が Type + Decode であるという条件を Row の定義から知ることができないからである。これを知るには split_x のシグネチャを知る必要があるが derive macro には不可能なはず。というわけで sqlx::FromRow ではこのような decode オプションを実装することができない。

sqlx-with

sqlx::FromRow の derive macro で decode オプションを実装できない原因は実装があまりに generic だからだと考えた。sqlx::FromRow を derive macro を使わずに手書きで実装する場合、普通は特定のデータベースを前提に実装する。実際に sqlx::FromRow のドキュメント https://docs.rs/sqlx/latest/sqlx/trait.FromRow.html#manual-implementation でもそのように案内している。 そこで具体的なデータベースを渡せるような derive macro に変えれば、decode オプションを実装できるだけの generic さを残しつつ sqlx::FromRow と同様の便利さを得られそうだ。実際にそのように sqlx_with::FromRow という derive macro を実装してみた。
https://github.com/eagletmt/sqlx-with

use sqlx::Connection as _;

#[derive(sqlx_with::FromRow)]
#[sqlx_with(db = "sqlx::Sqlite")]
struct Row {
    #[sqlx_with(decode = "split_x")]
    x: (i64, i64),
    y: String,
}

fn split_x(index: &str, row: &sqlx::sqlite::SqliteRow) -> sqlx::Result<(i64, i64)> {
    use sqlx::Row as _;
    let n: i64 = row.try_get(index)?;
    Ok((n, n + 2))
}

#[tokio::main]
async fn main() {
    let mut conn = sqlx::SqliteConnection::connect(":memory:").await.unwrap();
    let row: Row = sqlx::query_as("select 10 as x, 'hello' as y")
        .fetch_one(&mut conn)
        .await
        .unwrap();
    assert_eq!(row.x, (10, 12));
    assert_eq!(row.y, "hello");
}

db というオプションにデータベースを渡すことでそのデータベースを前提とした impl を生成でき、split_x のような関数も中で使えるようになった。sqlx::FromRow と異なり指定したデータベース以外には使えなくなってしまうが、現実的に1つの struct を複数の異なる種類のデータベース間で使いまわしたい場面はまず無いだろう。

おまけ: darling

sqlx_with::FromRow という derive macro を実装するために darling というライブラリを初めて使った。proc macro を実装するための derive macro を提供していて、derive macro を実装するには FromDeriveInput を使うと必要な syn の値にすぐにアクセスできる struct が得られて便利だった。

C のヘッダファイルの解析に bindgen を濫用するアイデア

Rust には bindgen というツール、あるいはライブラリがあって binding を書くときに非常に重宝する。
https://rust-lang.github.io/rust-bindgen/
これを濫用すると C のヘッダファイルを解析してマクロの定数値を読み取ったり構造体のサイズを調べたりといったことが手軽に可能そう。

たとえば errno の名前と値とメッセージの一覧を知りたいとき、こんなかんじで errno.h の中身から定義を取り出すことができた。

lazy_static::lazy_static! {
    static ref MACROS: std::sync::Mutex<std::cell::RefCell<Vec<(String, i32)>>> = Default::default();
}

#[derive(Debug, Default)]
struct MacroCollector {}

impl bindgen::callbacks::ParseCallbacks for MacroCollector {
    fn int_macro(&self, name: &str, value: i64) -> Option<bindgen::callbacks::IntKind> {
        if name.starts_with('E') {
            MACROS
                .lock()
                .unwrap()
                .borrow_mut()
                .push((name.to_owned(), value as i32));
        }
        None
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    use std::io::Write as _;

    bindgen::builder()
        .header_contents("wrapper.h", "#include <errno.h>")
        .parse_callbacks(Box::new(MacroCollector::default()))
        .generate()
        .expect("unable to collect macros");

    let out_dir = std::env::var("OUT_DIR")?;
    let mut file = std::fs::File::create(std::path::Path::new(&out_dir).join("errno.rs"))?;
    writeln!(file, "pub const ERROR_NUMBERS: &[(&str, i32, &str)] = &[")?;
    for (name, value) in MACROS.lock()?.borrow().iter() {
        let message = unsafe { std::ffi::CStr::from_ptr(libc::strerror(*value)).to_str()? };
        writeln!(file, "(\"{}\", {}, \"{}\"),", name, value, message)?;
    }
    writeln!(file, "];")?;
    Ok(())
}

当然こういう用途に使うものではないのでグローバル変数に結果を書き込むことになっているけど、build.rs 内ならまぁ許されるだろう。 本格的に解析するなら bindgen と同様に libclang を直接使ったほうがよさそうだけど、手軽に定数やシグネチャを拾うくらいなら bindgen を濫用するのが楽そう。

Axum を使ってみた感想

Axum は Tokio のチームから発表された Web アプリケーション向けのフレームワークです。
https://tokio.rs/blog/2021-07-announcing-axum
これを使って少し Web アプリを書いてみたりしたのでその感想を書いてみます。

Rust にもいくつか Web フレームワークがあって個人的には https://github.com/flosse/rust-web-framework-comparison#high-level-server-frameworks の表がしっくりきているんですが、async/await に対応していてハイパフォーマンスで書きやすいものといえば Actix Web が一番かなという状況でした。 ただ、Actix Web は HTTP のレイヤに hyper を利用しておらず独自の実装になっていて、Tokio や hyper をベースにしたライブラリが多い中でちょっと使いづらい面がありました。 そのためか最新の安定版でも Tokio v1 対応が済んでおらず、最近の Actix Web は実質的に beta 版を使うしかない状況になっています。 hyper ベースのフレームワークとしては warp も最近人気ですが、高レイヤの便利機能が不足していたり filter の概念が独特で型が複雑になりがちだったりで、個人的には Actix Web と比べると使いやすさはかなり落ちるという印象です。

そんな状況でリリースされた Axum は Actix Web にかなり似ている使い勝手を提供していて、一番の印象は Actix Web の対抗になれそうというものでした。 Tokio チームが開発しているので当然 Tokio をベースにしていて、hyper、tower、tracing といったデファクトとなりつつあるスタックの上に Axum は作られています。 gRPC サーバでは既に Tonic がデファクトになっていて、それと似たスタックの上に Web サーバを実装できるところも Actix Web より使いやすそうなところです。

実際に ISUCON11 の予選問題の初期実装を Actix Web 4 (beta) から Axum 0.4 に書き直してみたのがこれです https://github.com/isucon/isucon11-qualify/compare/main...eagletmt:rust-axum 。 ルーティングに関しては ISUCON11 予選では Actix Web のマクロを使って書いていたので一見差分が大きいですが、Actix Web にもマクロを使わない書き方があって、それと比べると Axum はかなり似ていると思います。 エラーの表現は Actix Web の ResponseError トレイトの代わりに IntoResponse トレイトを実装するようなかんじになっていて、ハンドラからエラーを返すときは Actix Web と同じように Result で返せばいいだけです。 ハンドラの書き方はリクエストの情報や共有データを extractor 経由で取り出して引数で受け取るという形になっていて、これも Actix Web と非常に似ています。Actix Web では app_data() で DB のコネクションプールのような共有データを登録して web::Data で受け取っていたのに対して、Axum では AddExtension layer として共有データを登録して extract::Extension で受け取るようになっていますし、パス内のパラメータの参照や JSON ボディのデシリアライズやクエリ文字列の取り出しなども同じように extractor 経由で行えます。 actix-session の CookieSession に相当するような便利なものはまだ無さそうだったのでそこは自作しました https://github.com/eagletmt/tower-cookie-store 。FromRequest トレイトを実装すればいいという点も非常に似ていますね。 ちなみにパフォーマンスについては少なくとも ISUCON11 予選問題の初期状態においては Axum のほうが若干上でした。

というかんじで、Actix Web と同じように使えて Actix Web の不満が一通り解消されているので、個人的には今後は Web フレームワークの第一候補として使っていこうと思えました。 まだリリースされたばかりで破壊的な変更もよくある https://github.com/tokio-rs/axum/blob/main/axum/CHANGELOG.md フェーズですが、今後にかなり期待できます。