「オレのワタシの記念日」に応募したら当選した

3月のある日、妻が突然「俺のイタリアンでロッシーニ祭りをやってるから食べてみたい」と言ってきた。 ロッシーニといえば、牛フィレ肉の上にフォアグラを乗せ、トリュフのソースをかけた濃厚でハイカロリーな料理だ。

ロッシーニはイタリア人のオペラの作曲家で、そして美食家だったそうだ。上記の豪勢な料理は彼が考案して、今はフランス料理で「ロッシーニ風」と呼ばれている。

ただ、今回はその料理について語ることがメインではない。キャンペーンに当選したことを紹介する(= お店にお礼の記事を書いて店を宣伝する)が目的だ。

話を続ける。俺のイタリアンに行った時に、妻が「俺の」のアプリをインストールした。すると、アプリ内のバナーに「オレのワタシの記念日」というキャンペーンが表示された。

このキャンペーンでは太っ腹にもフルコースを無料で食べさせてくれるらしい。 ただし、条件は「二人の記念日と『俺の』に関する思い出」を自由入力欄に記述することだ。

6年前、たまたま妻と付き合い始めたその日に俺のイタリアンに行ったので一応エピソードはある。 妻が応募してみるというので、私は「ある程度の分量を書いてみたら選ぶ人の目に留まるんじゃない?まあ落ちても家で祝おう」と話したところ、有難いことに当選した。

https://www.oreno.co.jp/2022/03/10/kinenbi

後から店で聞いた話なのだが、5月だけで200通の応募があったそうだ。 「オレのワタシの記念日」では365日のうち毎日1組が選ばれるので、1日に6~7組が応募していることになる。

キャンペーンが知れ渡るとこれからさらに競争倍率が高くなるだろう。キャンペーンが始まってすぐに記念日が重なっていていたため、早めに応募できてよかった。

Grand Maison ORENO

さて、このキャンペーンでは、料理は新店舗で振るまわれる。その新店舗の名前は Grand Maison ORENO(グランメゾン俺の。以下グランメゾン)。2021年に開店したそうだ。

oreno サイトトップページ

https://www.oreno.co.jp/grandmaison_oreno/

ネットで紹介ページを見てみると、シェフもギャルソン(料理を運んで説明してくれる人)もソムリエも超一流を集めましたと書いている。

これはこちらもちゃんとしないと、ということで当日は普段あまり着ることのない襟付きのシャツを引っ張り出してきてアイロンを当てて準備した。

レストランに行く前に身だしなみを整える

しっかりしたレストランに行く時はドレスコードを満たすことはもちろんのこと、しっかりした服装をした方がいい。私が大学時代にレストランでアルバイトをして学んだことだ。

レストランでは、食事をサーブする側にお客さんの社会的地位や名誉はわからない。そのような肩書きは帰り際に名刺を貰う時に初めてわかるものだ。だから地位名誉に関係なく、身なりをきちんとしておくだけで丁重に扱ってもらえる。

このことを「ドレスコードは侮られないため。服をちゃんとするのはその場に相応しい人になるため」と自分は考えている。

ポイントとしては、何も高い服を着たり新しい靴を履く必要はないところだ。服のブランドのタグは外から見えない。代わりに、シャツやジャケットのシワを伸ばし、ズボンのシミや靴の汚れがついていないことを確認すればそれでOKだ。

当日はそのように服装を整えてお店に向かった。

当日のコース料理

店は大手町のサンケイビルの地下にある。店の中はコンサートホールのように、中央にあるステージと巨大スクリーンを中心にして扇状に座席が広がっている。

ステージの上にはピアノがあり、食事中に演奏が楽しめるのだ。ピアノは「ピアノの森に出てくるやつだ」と妻が言っていた。その値段は家が一軒立つくらいのものらしい。

店内の風景

https://www.atpress.ne.jp/news/298719 より引用

座席についてシャンパンを注いでもらった後、コースが始まった。

座席

料理は一品一品どれも目で見て楽しいし、とても美味しい。素材もきっといいものを使っているんだろう。どれも上品で濃厚だけどサラリとした味わいだった。

「トリュフパイの実」可愛らしいアミューズ。焼きたてでとても美味しい

カクテルグラスにムースとジュレの料理が入っている

「オマール海老のコンソメ “トマトのムースカクテル仕立て”」ウニが乗っていて美味しい

平たい皿の上に筍とエビが乗っている

「旬感の一皿 “四季の贈り物”」筍とエビが美味しい

皿の上に白い泡と貝が乗っている

「海の幸の一皿 “UMAMIのオーシャン”」泡の下に白身魚が隠れている。ふわふわとした食感で美味しい

牛フィレ肉とフォアグラが皿に乗っており、ソースが下に敷いてある

「牛フィレ肉とフォワグラのロッシーニ “黒トリュフのペリグーソース”」牛フィレ肉が柔らかく、フォアグラには全く臭みがない。ソースも口の中で混ざって噛めば噛むほど美味しい

四角い小さいパン

「俺のベーカリーのパン」もっちりとして美味しい

「一期一会のデセール」アイスクリームもチョコレートケーキもとても美味しい

(全部のキャプションの終わりが「美味しい」なのだが、実際に美味しいので仕方がない)

食事の最後に記念日のプレートを持ってきてくれて、そこで記念撮影。盛り付けも綺麗でとても美味しかった。

行ってよかった

最後に店の方が話しかけてくれた。グランメゾンの店長とシェフは、元々俺のイタリアン青山店にいたそうだ。それを聞いて妻が「3年前の記念日にも青山店に行ったんですよ」と伝えたら、「その時は確実にいました」と答えてくださった。すごい偶然だ。

実はこの日の昼に、アメリカに行けないと確定して落胆していた

料理も美味しかったし店の人も優しく、いい思い出になったねと妻と話しながら店を後にした。

「オレのワタシの記念日」のキャンペーンはまだまだ始まったばかり。気になる方はぜひ応募してみてください。

https://www.oreno.co.jp/2022/03/10/kinenbi

プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

会社の賞のアメリカ旅行

事の始まりは、会社で妻の所属するチームが社内賞を受賞したことだった。 その賞には毎年全社から50名ほどが選ばれ、選出された人全員に副賞が与えられる。特に優秀な成績を収めた人はポルシェやロレックスの時計が貰えるらしい。が、それはウチとは無縁な話だ。

50人全員が貰える副賞のはクリスタルの盾とアメリカ行きの旅行だ。 その旅行の行き先は毎年変わる。去年はハワイだった。飛行機のチケット代もホテル代も、ホテルでの飲食費も全部会社が負担してくれる。しかも滞在期間は1週間。これを妻から聞いた時に流石は外資系の会社だと舌を巻いた。

そして、幸運にも今年の社内賞に妻の所属するチームが選ばれた。 2022年の3月頃のことだ。その知らせを聞いて夫婦でひとしきり喜んだ後、二人の話題は今年の旅行先に移った。

しばらくして会社からアナウンスがあり、今年の旅行先はフロリダ州のオーランドに決まったとのことだった。 宿泊予定先はフォーシーズンズ・リゾートという、本場のディズニーランドの隣に立っている5つ星ホテルだ。しかも滞在予定期間はゴールデンウィークの翌週で、妻はもちろん仕事を休めるし、自分も有給を取ればさらに1週間仕事を休むことができた。はずだった。

しかし、タイトルに記載した通り、思いがけないあることが原因で私のVISAが免除されず、その無料のアメリカ旅行に行けなくなってしまった。 事の顛末をエッセイとして書くことで行き場のない思いを供養したい。

入念な事前準備

コロナ関連の書類手配

今年の行き先が決定した後、情報収集と必要な手続きを進めていった。

まず調べたのはコロナ関連のことだ。現在のアメリカは2回のワクチン接種証明書があれば、入国してからの自己隔離は不要になる。 夫婦ともにブースターショットまで打っているのでワクチンに関しては問題なしだ。そこで、海外で使えるワクチンの接種証明書を郵送で市から取り寄せた。これでコロナ関連のことは大丈夫だ。

事前の休暇申請

次に私の休暇申請だ。 ゴールデンウィーク明けの平日5日間を丸々有給消化するというのは正気の沙汰ではない。と思ったのか、1on1でそれを告げた時 にマネージャーは唖然とした表情を浮かべた。私があえて理由を告げずに「1週間休みます」と話したのが主な理由だろう。驚くであろうことは事前に想像できていた。

どちらかというと「会社が、仕事が嫌になったのか」「転職活動を裏でするためなのか」と思われたのかもしれない。そこですぐさま、ちゃんと理由があって仕事が嫌になったわけでは全くないと伝えたら、いきなりでびっくりしたとマネージャー。そこで二人で笑い合った。 緊張と緩和だ。ありがたいことにマネージャーは妻のことを労ってくれた。

休暇の話は前もってしておくとスムーズだというのが社会人7年目の私の経験則だ。 3月のうちに5月の休暇の話をマネージャーとも自分が所属するチームともして、特にチームに対してネガティブサプライズにもならないように配慮した。そして、自分が休んでも支障がないように4月中の仕事を進めていた。

飛行機のチケットの手配

最後に、フロリダまでの飛行機チケットの手配。 アメリカ国内でのトランジットを含めて、10時間以上の長旅になる。座席の値段を見ると一人往復30万円超だった。高いけど会社が払ってくれるのは本当にありがたいねと二人で感謝して予約ボタンを押した。

妻は3月末にチケット代をドル建てで決済しており、4月に経費申請して会社から円建てでチケット代が振り込まれた。この期間に急激に円安になったため、日本円で5万円ほど儲かっていた。 妻の支払いは3月のドル円レート、会社の経費精算は4月のレートで計算されていたからだ。

この時は「いやらしい話だけど、タダで旅行に行けるのにさらに儲かっちゃったね」と言っていた。結局全額返すことになることはつゆほども知らずに。

コロナ対策を頑張った4月

さて、この段階ではまだアメリカに行けると考えていたものの、コロナ関連のことだけは未知数だった。ワクチンを接種していても出国できない可能性があるからだ。 フライトの前日に必ずPCR検査を受けなければならず、万が一陽性だった場合に日本から出ることができないことになっているのだ。

このため、4月はコロナ対策をいつもより入念に行った。具体的にしたことは、4月中旬から極力人に合わないことだ。 久しぶりに会おうというお誘いがあっても泣く泣く断った。もちろんゴールデンウィークの予定は何も入れない。友人とも合わないし帰省もしない。

ただし、4月下旬に遠方に住む妻の家族が家の近くに来た時だけは会いにいって一緒に食事をした。アメリカに行くために家族に会わないよりは、家族に会ってもアメリカに行けない方がマシだよねと夫婦で意見が一致したからだ。妻の家族が予約してくれたレストランの席が外の席でオープンエアだったのでホッとした。この時は食事を済ませると早々に解散した。

4月もゴールデンウィークも我慢できたのは、アメリカではもうマスクを外して活動しているという話があったからだ。 フランスの高名な詩人も「苦しみの後には楽しみがくる」(「ミラボー橋」ギヨーム・アポリネール)と歌っているではないか。

ESTAの申請で事件は起こった

我慢の4月が終わり、5月のゴールデンウィークに入った。 近所のショッピングモールでスーツケースを新しく買った。現地の夕食会ではドレスコードがあるから服も準備しないと。成田空港発の飛行機は朝10時出発だから空港近くにホテルも取らないと。その前にESTAの申請をするかと夫婦でダイニングテーブルに座ってパソコンを並べた。

ESTA(アメリカのVISA免除制度)はオンラインで申請できる。 ウェブページは日本語版の申請画面もあり、特に困ることなく申請を進められた。最後の方で「重い病気はあるか」「テロリストとして活動したことがあるか」のような物騒なチェックリストがあった。もちろん全て「いいえ」を選んだ。海外への渡航歴を除いては。

チェックリストの最後に「2011年3月以降にイラン・イラク・シリア・北朝鮮…等の国への渡航歴はあるか。あるならその目的は何か」という項目があった。 そういえば、と古いパスポートを引っ張り出してきた。大学1年生の時にシリアを含む中東の国に旅行に行ったことがあったからだ。

シリアの思い出

高校生の時に世界史にハマり、特に世界の歴史「イスラム世界」 という文庫本を読んだ。それから中東の歴史・文化の豊かさを知り、いつか旅行したいと思っていたのだった。大学に入学して同じく中東に興味がある友人と、春休みを使ってトルコ、シリア、レバノンに行く計画を立てた。

中東は特に未知の世界だ。恐る恐る行ってみると、意外にもシリアはいい国だった。外国からの観光客と見るや、大人も子供もはにかみながら英語で「Welcome to Syria」と話しかけてくれるのだ。こんな国は他にはない。 首都ダマスカスで最古のモスクであるウマイヤモスクを見学したり、スーク(バザール)の入り口に立つスレイマン大帝の銅像を見て、歴史と人の営みに思いを馳せた。

2011年のシリアのビザ

惜しいことにデジカメのデータが消えてしまってその時の写真は手元に1枚もない。残っているのは頭の中の記憶ばかりである。それでも、確かにシリアの渡航歴はある。

渡航期間

次に渡航の期間だ。ESTAのチェックリストには「2011年3月以降に〜」と書かれている。自分は惜しくもこれに該当している。 忘れもしない、2011年3月は東日本大震災の月だ。自分はこの時シリアにいた。

当時ガラケーを持って旅行していた。国際電話で安宿から実家に電話してみたのだった。電話回線はとても貧弱で音声は途切れ途切れ。どうなっているか状況ははっきりしない。安宿なのでパソコンも置いていない。海外にいるから正確な情報が入ってこない。

結局実家は無事だったのだが、あの電話は地球の歩き方に載っていたシリアの宿からかけた。これは間違いない。大学の春休みを使っての旅行だったし、シリアに滞在していたのは2011年の3月だ。これでESTAで尋ねられている期間に該当することが確定した。

ただし、滞在日数は4日あるかないかだ。自分が出国してから2週間後にシリアで内戦が始まったというニュースを日本で聞いて心を痛めたこともよく覚えている。

一抹の不安もあったが、やましいことは何もない。渡航歴の有無を尋ねる項目に「はい」にチェックをして、目的として観光を選択した。

ESTA申請がリジェクト(拒否)された

そして残りの項目を記入し、申請手数料を払った。ESTAの申請結果は72時間以内にわかるらしい。申請結果は明日確認しよう。でもその前に、確認方法だけは知っておかないとな、と思って申請IDをESTAの確認画面に入力した。

まさかすぐに結果が出ているわけがないだろうと思って「Pending」でも表示されるかなと思っていた。しかし、次の瞬間、目に映ったのは「Reject」(拒否)の文字だった。

一瞬混乱した。やはり渡航歴がダメだったのか。しかし画面をよく見ると、「VISA(ビザ)を申請してください」という案内が表示されている。 ビザならアメリカの大使館で取得しなければならない。Google でビザ取得までの期間を調べたら、10日程度で発行されると書いている。

この日は5月3日で飛行機は次の日曜日の5月8日。電話番号を調べてすぐに大使館に電話した。 誰かが電話に出た。それはゴールデンウィークで休みですという案内音声だった。

誰かが事前に録音した、アメリカドラマの日本語吹き替えのような日本語なのにやたら抑揚のある音声。普段は人の心を開かせるようなこの明るさが、が、この時は逆に人を冷たく突き放すように思えた。「No。あなたに取る手段はありません」と笑顔で言われているようなものだ。

もう無理だ、アメリカに行けない、代替手段もない、詰んだ…と悟った瞬間、急に体が重くなった。 そのまま椅子からずるりと滑り、床に大の字になって仰向けで天井を見つめた。

与えられるはずだったのに失ってしまったもの

しばらくの間、頭の中を後悔が駆け巡る。

「シリアの渡航歴に『いいえ』をつけていれば。あれからパスポートを更新したので番号も変わっているからわからないかもしれない」「いや、ただ観光で旅行しただけなので自分に後ろ暗いところは何もない。それにあの時は全く危険ではなかった。それよりアメリカについた後の入国審査でバレてそのままトンボ帰りになる方が最悪だ」

「アメリカに行けないリスクはコロナだけだと思っていた」 「妻は前から『自分一人では楽しくないから、あなたが行けなければ私も行かない』と言っていた。妻が仕事を頑張って貰った賞なのに妻も行けなくなってしまって申し訳ない」「色々前もって行動して万難を排したつもりだったのに」「もっと早くに行動していれば間に合ったのに…」

妻は「仕事を頑張って社内賞をもらえたこと自体が嬉しい。旅行はおまけだから今回は仕方ない」と言って慰めてくれている。それが唯一の救いだ。 それでも、与えられるはずだったが失ってしまったもののことを考えてしまう。

無料で泊まれるはずだったフォーシーズンズ・リゾート。調べてみると一泊20万円からだ。そんなに良い部屋に泊まったことはないし、これからもないだろう。

滞在中に会社から毎日貰える800ドル(今の為替レートだと約10万円分使える)の小遣い。ルームサービスとかスパに使えたはずだった。

ディズニーランドにも行けたはずだった。希望者は入場チケットを会社が手配してくれ、しかも往復のバスまで出してくれる予定だった。

プールの一部エリア貸切、バーでカクテル飲み放題…。得るはずだったのに失ったものはキリがない。 「上下ともに白」というドレスコードありのディナー会が予定されていたので、これに行かなくて済むようになったので多少気持ちは楽だったが。

今から考えると飛行機のチケット手配と同時にESTAの申請をしておけばよかったのだ。 ESTAの手続き自体は十数分、出国の3日以上前に行えば良いと聞いていたので後回しにしてしまったのだった。

飛行機のチケットを取ったのは3月だったから、あの時行動していればビザの申請にも十分間に合っていた。完全に自分のミスだと悔やんでも悔やみきれない。

妻の会社にも行けなくなったことを連絡して、飛行機のチケットをキャンセルした。ああ、座席を確保したあの飛行機は自分達を乗せず、この夏空を軽快にも飛んでいくのだ。私はそれを地上から見上げる他にできることが何もない。

結局のところ

最後にもう一度、ESTA申請が遅れたから行けなくなったのかと自問してみる。 しかし、遅れたといっても普通なら問題ない期間での申請だ。人は合理的であっても全知ではない。ESTA申請には間に合うように行動する程度には合理的なのだ。しかし全知ではないので、それが却下されるまで、シリアの渡航が問題になるとは全く思っていなかった。

ではシリアへの渡航、過去の自分の行動が原因なのだろうか。いや、あの頃シリアは全くの平和だった。そうであれば、たまたま自分が滞在した月がアメリカの設定した「2011年3月からの渡航歴」という基準月に重なってしまったからなのか、と考えるとどうもこの偶然の結果が今なのだろうと考えることにしている。

できる限りのことはした。コロナ対策や書類取り寄せ、情報収集、各所への連絡。

しかし、結局のところ、ネットに弾かれたボールはどちら側のコートに落ちるか誰にもわからない。 自分にとってのネットがこの2011年3月ということだっただけだ。私の渡航歴というボールが偶然にもこちら側、つまりアウト側に落ちてしまっただけなのだ。

「ジョジョの奇妙な冒険 Part7 スティール・ボール・ラン」より引用

過ぎたことは仕方ない。昨日はしっかり落ち込んだので、一日経ってこうして文章を書けるほどに元気が出てきた。ちゃんとコロナ対策をしつつ、しばらく我慢していた銭湯にいくことにする。

プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

3年前に抱いていた Rust に対するイメージ

「コンセプトから理解するRust」(Amazon)という書籍を読んで Rust に再入門してみました。 実は2019年に Rust のチュートリアルである「The Rust Programming Language」(通称 TRPL)を写経したことがあります(GitHub)。

しかし、当時の自分は PHP のバックエンドエンジニアとしての経験が2年ちょっとあるくらいで、TypeScript にもまだ入門しておらず静的型付け言語の経験はほとんどなかったように記憶しています。

当時から Rust は所有権などの概念が難解で初心者を拒むものの、 Stack Overflow のアンケートで開発者から人気な言語として高い地位を誇っているというイメージがありました。

https://twitter.com/Panda_Program/status/1108640118586277888?s=20&t=dFPDY_g-c-DV14ZxjCLd1Q

自分自身、PHP 以外にもサーバーサイドで使える言語があるといいなという思い、Rust は学習が難しいものの一度学べば資産となり他でも使える考え方を得られるかもしれないという考え、Rust に難しいものを学んでみるというちょっとした憧れみたいな感情を持っていました。

ただ、2019年春頃時点では日本で Rust を使っているメジャーな会社がなく、転職サイトでヒットしたのは外国の方が日本で設立した仮想通貨関連の会社くらいでした。今とは全く状況が違いますね。このため、がっつり使うまで学ぶというより勉強のためにやってみようかな、でも難しそうだなと二の足を踏んでいました。

「The Rust Programming Language」で Rust に入門した

そんな折、「六本木ではたらくソフトウェアエンジニアへのよくある質問とその答え (FAQ) 」 という Rust を支持する記事を読み、やっぱり Rust って良いんだ思いました。

それをきっかけに冒頭で紹介した TRPL を写経していったのですが、文章が難しくて内容が頭に入りづらかったです。文章を読み、サンプルコードを見ながらそれをエディタに入力し、手元の環境で実行。一つ一つの章を完璧に理解するよりもまずは概観することを優先し、腑に落ちないところがあってもとりあえず先に進むようにしました。

スタックやヒープ、enum、スライス、ハッシュマップと知らない単語がたくさん登場する上に、特に文字列の扱いには困りました。Rust には文字を扱う型に str、String、char があります。なんでこんなに大変なんだろう、””で囲むだけの PHP は楽だと思ったものです。

一章進んでは知らない単語を調べ、コードを写経する。タイポしては長いエラーメッセージに驚き、エラーを読んで理解できるところは自分で修正し、わからないところはサンプルコードを見て答えとする。これにとても時間がかかりました。

また、友人が Rust でミニプログラムを作ったという話を聞いてすごいなあと思いつつ、自分もやればできるはずと気合を入れて TRPL を一通り終わらせました。

TRPL 1周目でわかったこと、わからなかったこと

最後の方は疲れ切っており、よく理解せずに写経するだけになっていました。それでも、TRPL のおかげで理解できることもありました。

値を変数に束縛し(代入じゃない!)、スコープを抜けたらメモリから値を解放すること、値の参照があること(先輩からPHPではご法度と教えてもらっていました)、構造体とメソッドは別で定義するため、データとそれを操作するメソッドをクラスとしてまとめるオブジェクト指向とは書き方が異なること、初心者の最初の関門とされている所有権と借用の考え方も理解はできました。初心者には所有権よりライフタイムの方が難しいだろうと思いました。

他にも正の整数の型である usize 型があったり、match式や if let が便利なこと、null がないことやドキュメントを自動生成できること、テストや formatter がビルトインであることなどはちゃんと頭に残っていました。

しかし、それでもわからなかったことの方が多いです。 <T>のように書くジェネリクス(関数定義のどこに書けばいいか覚えられなかった)、トレイトやトレイト境界、スライス、ライフタイム、Box、Rc、RefCell、Arc、Mutex、クロージャ、イテレータ(とりあえず !vec を iter())。メソッドの第一引数の &self ってなんだろう。なんであったりなかったりするんだ。Option と Result<T, E> はわかったようなわからなかったような感じ(とりあえず unwrap する)。

わからないことは多かったのですが、それでもいい影響はありました。 可変と不変の考え方を知ったので、PHP でも変数の再代入を極力避けるようになりました。型を書くことは面倒なのではなくプログラムが堅牢になるとのだとわかったので、引数と返り値の型が書ける場面では必ず書くようにしました。

TRPL を終えた後、Rust で TDD の Bowling Game を実装してみました。ただ、それでもちゃんと理解したという感覚は得られなかったので、人には Rust を触ったことがあるとは言えませんでした。

その間にもネットで Rust の記事が増えているのをみて使用人口の増加を感じたり、Twitter で Rust に入門したという人が増えていくのをみて素直にすごいなと思いました。一方、それを見るたびに自分は再トライしてもまだ理解できないんだろうなと思っていました。

再入門のために「コンセプトから理解するRust」を読んだ

しかし、個人的なことながら年初に2022年の目標は「深掘りすること」と決めていました。 また、フロントエンド周りでは SWC や Rome をはじめ、Rust で書かれるツールが年々増えています。そこでまずは、Rust と同様に入門に挫折したことのある GraphQL の書籍を先日読んでみました。 この書籍はスムーズに読み進めることができたため思わず自信を得ました。

それがきっかけで、Rust も今ならわかることがあるのではないか、少なくともライフタイムとトレイトくらいは理解してみたいと考えました。 しかもタイミング良く初心者向けの書籍が発売される上、その書評 にも以下のように書かれており期待できます。

「所有権」や「ライフタイム」あるいは「トレイト」など、最初他の言語から Rust に入門すると理解に苦労する概念をとくに丁寧に説明しています。

ということで「コンセプトから理解するRust」を買って読んでみました。Software Design の自分の文章をチェックしてくださった編集者の @akito912912 さんが本書を担当したということも購入を後押ししました。

「あれ?今は理解できる」という感覚でどんどん読み進めていった

以前わからなかったライフタイムとトレイトを学ぶため、先にその章から読み始めました。すると、驚いたことに以前と違ってよく理解できました。 これはもしや…と思って Option、 Result の箇所を読んでもコードを理解しながら読めました。これはひとえに本書の丁寧な解説によるおかげです。

特に str 型と String 型の違いを解説する次の一文を読んで目から鱗が落ちました。

配列方の高機能版としてベクター型があったように、str型の高機能版としてString型があります

配列(固定長)とベクター(可変長)の解説を事前に読んでいたので、これはわかりやすい解説だと感じました。その他にも変数と値の関係の比喩も、よくある箱ではなくラベルで解説されており、とても理解しやすいものでした。

本書にはこのように「この一文があるから Rust に対する理解が捗った」と思うような表現がたくさんあるので、次にいくつか引用します。

なお、ここでは全く関係ないことですが、「目から鱗が落ちる」という諺はイタリア語で「目からハムが落ちる」というそうです。

Rust の理解を助ける表現を書籍から引用する

所有権

既存の変数を新しい変数と = で結びつけたときに、新たなメモリ領域が確保され、既存の変数の値を新たなメモリ領域にコピーする方式を「コピーセマンティクス」と呼んでいます。

既存の変数を新しい変数に = で結びつけたときに、新たなメモリ領域を確保せず、所有権を移動させて新しい変数のラベルに付け替える方式を「ムーブセマンティクス」と呼んでいます。

所有権という考え方を導入すると、メモリ上に格納された値とラベルである変数が1対1に対応します。

プリミティブ型には、Copyトレイトが実装されていて、ほかの変数に束縛しても所有権を失わないことが共通した特徴です。

借用

値の代わりにリファレンスを関数の引数に渡しても、所有権が関数内の変数に移動しない

リファレンスのスコープは参照する元の変数のスコープよりも小さい必要があります。これを所有権の言葉で言い換えれば、「リファレンスは参照する元の変数の所有権のライフタイムを超えて使うことができない」ということになります。

スライス

スライスはリファレンスであり、別に実体があるものに対する「ビュー」を与えています。所有権は持ちません。

文字列リテラル

文字列リテラルに束縛された変数は &’static str 型と推論されます。

文字列リテラルが示す文字列はプログラム実行の初期にメモリの「静的領域」と呼ばれる領域に配列のように配置され、そのスライスとなります。

「静的領域」はプログラムの起動から最後まで割り当てられていてサイズが不変な「静的変数」を配置する領域です。

構造体

クラスを持つ言語では、クラスの宣言の中で変数やそれを操作するメソッドを定義しますが、Rust では構造体の関連関数やメソッドの定義は別のブロックで行われます。

Box

生ポインタを使わずにヒープ領域にデータを配置できる Box型があります。

Box型を用いるもう1つの場合が、コンパイルのときには方が決まらない場合

トレイト

ある動作に関連するメソッドを列挙したものを「トレイト」と呼んでいます。類似のものにJavaやGo言語の「インターフェース」、Haskellの「型クラス」があります。

型パラメータに加える「トレイトを満たしている」という条件のことを「トレイト境界」と呼びます。

ある型がトレイトを満たすためには、トレイトで宣言されたすべてのメソッドがその型に実装される必要がありますが、デフォルトの実装があるメソッドはすでに実装済みになります。

その他

型やライフパラメータを固定せずにパラメータ化し、構造体や関数・メソッドを定義することをジェネリクス(generics)と呼んでいます。

Rust に入門して破門されたが、その後の学習が穴を埋めた

「コンセプトから理解するRust」の読書体験は自分にとって記憶に残るものとなりました。 本書を読み進めて自分に理解できることが増えていることに安堵すると同時に、実は3年前の自分に足りていなかったのは Rust に対する理解というより、Rust が取り入れている概念なのだと気付いたからです。

https://twitter.com/Panda_Program/status/1496350377197080581?s=20&t=dFPDY_g-c-DV14ZxjCLd1Q

ここからは個人的な体験ですが、印象深かったので書き残しておきます。

例えばジェネリクス。ジェネリクスは TypeScript を通して学びました。例えばトレイトとトレイト境界。これはインターフェースと抽象クラスを PHP で学んでいたので理解できました(なお、PHP にも Trait はあります)。

他にも HashMap は JavaScript の Map で、イテレータはデザインパターン(GoF本、Amazon)のイテレータパターンで、map や reduce を多用する使う関数型のプログラミングの作法は React から、メソッドの第一引数に &self で構造体自身を受け取れるのは Python のクラスから学びました(以前 AtCoder に挑戦していた時期があり Python を少し触ったことがありました)。

また、Result<T, E> については JS Conf 2021年の「関数型プログラミングのデザインパターンひとめぐり」 という発表のスライド6ページ目から、例外が発生する可能性がある関数で値またはエラーをラップして返すことができるということを知りました。なお、発表内容は自分にとって難しく全然理解できていませんが、少しでも役に立つところがあったので聞いてみてよかったと思いました。

文章に書いてしまうと3段落ですが、背後に積み重ねがあったから今スムーズに本を読み進められると思うと感慨深いです。

まとめ

Rust に入門したけど理解できず破門されたと思って遠回りした結果、Rust で取り入れられている考え方をそれぞれの別の場所で学び、結果的に Rust を理解できるようになっていました。 本書は Rust を理解するための最後の1ピースだったのです。

学習では一般的に、「基礎を固めて初めてと発展的な内容が理解できる」と言われています。学校でよく聞いた表現ですね。これは、知識が互いにリンクしていることを表しています。ある知識は別の知識と関連しており、直接、間接を問わず複数の知識の上に新たな知識が生まれるものです。

そして、ある領域で得た知識が別の領域で活かせる場合、それは学習資産と呼べるでしょう。学習コストと学習資産の話は @lacolaco さんの 「Angularの学習コストは本当に高いのか?」という投稿で例とともに端的に解説されています。

記事の内容は、Angular とそれを取り巻くツールを一通り学ぶには時間と労力がかかる。得た知識と経験はAngular でしか活かせないものと他でも活かせるものに分けられ、後者の要素が多いため学習が大変でも学ぶ価値があるというものです。

Rust も同様に、モダンなプログラミングのベストプラクティスを取り入れたような言語設計となっているため、学習コストは大きくとも学習者にとって今後の大きな資産になるでしょう。

2022年時点では無料で公開されている Rust の資料も多いです。@helloyuki_ さんの 「Rust を始めるための資料集」 という記事が良質なリンク集となっており、ここから自分に合う教材を探してみても良いでしょう。

さて、世の中の流れにはトレンドとサイクルの2つがあります。トレンドは優勢ならさらに優勢、劣勢ならさらに劣勢と一方向に進む動きです。サイクルは定期的に人気・不人気などを繰り返すものです。サイクルの例を挙げると「あるツールがたくさん生まれた後に有力な一つが覇者となり、そのツールに不満が溜まるとその代替ツールが雨後の筍のように生まれてくる」というようなケースです。

Rust が広まっていくことはサイクルではなくトレンドでしょう。これから先も Rust の使用人口は増え、採用される領域が広がっていくことと思います。Vercel に勤務している Lee Robinson 氏は自身のブログで「Rust は JavaScript のインフラの未来を担うだろう」と書いています。

https://twitter.com/Panda_Program/status/1460632516562681857?s=20&t=_LPaMz_A97waeDvWPt2CJg

本業で使う使わないに関わらず、今が Rust を学ぶ良い機会なのではないでしょうか。最後に、本書の中で Rust を学ぶにあたり勇気づけられる一文を紹介して、筆を置こうと思います。

一発でコンパイルエラーがないコードを書こうと思わず、コンパイラのエラー情報の助けを借りながらコードを書き進めることが Rust のコードが書けるようになるコツです。

これは自戒ですが、本を読んでわかったというだけでは真に理解したとは言えません。コードを書き、コンパイラとの対話を通して、Rust に対するメンタルモデルを構築していきましょう。

単語集

Rust、cargo、クレート(crate)、モジュール、所有権、リファレンス、ライフタイム、コピーセマンティクス、ムーブセマンティクス、束縛、ミュータブル、イミュータブル、変数の再宣言、シャドーイング、プリミティブ型、スライス、構造体、フィールド、メソッド、Option、Resukt<T, E>、match 式、if let、HashMap<K, V>、Vec、Box、ジェネリクス、動的ディスパッチ、静的ディスパッチ、ゼロコスト抽象化、トレイト、トレイト境界、マクロ、イテレーター、map、filter、fold、reduce、並列実行、非同期処理

RustBook
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

バックパッカーの間では「沈没地」という言葉が知られている。 バッグ一つで海外の安宿を巡り歩き、旅行代を出来るだけ浮かそうとするバックパッカー。彼らが沈没という単語を使うなら、それは飛行機ではなく船で移動する際の悲劇ということだろうか。

沈没という単語から海を連想するかもしれないが、船は全く関係ない。沈没地とは、ある都市や宿の居心地が良すぎるので、次の目的地に進むのに腰が重くなったり、いつまでもそこにいたいと思わせるようなところだ。

それは夕日が綺麗な海辺だったり、治安がいい上に安くて飯がうまい都市だったり、のんびりと過ごす人たちが集まる日本人宿であったりする。学校や塾、会社の通勤や会議であくせくしていた毎日を忘れさせてくれる、そんな場所だ。

バックパック旅行にもちろん決まったスケジュールなんてない。道端を歩く犬を見たり(狂犬病の恐れがあるので近づいてはいけない)、外で麻雀やトランプに打ち興じるご老人たちを横目に旧市街を散策したり、二段ベットが並ぶドーミトリーで一日中ゴロゴロしても良い。全ては自分次第である。

例えば、インドのニューデリーは有名な沈没地だそうだ。2017年に旅行した際、宿泊したニューデリーの日本人宿でそう教えてもらった。自分がそこにいたのは夏だった。昼間の外気温は40度前後まで上がり、今まで経験したことがないような暑さだった。

昼はみんな寝ている。暑さで体力が奪われるのだ。安宿へのチェックインは受付の前に置かれているベンチに寝ている男を起こすところから始まる。生産性なんて言葉はクーラーの効いた部屋にいるホワイトカラーのものだと実感した。ニューデリーの夏は暑く、何かをしようとする気力が削がれる。

街の人が行動するのは夕方からだ。夕方になって陽が落ちて、初めて街中から子供たちが外で遊ぶ声が聞こえ始める。

宿で安全で美味しいと教えてもらった店のマンゴージュース。だが、この後案の定腹を下して3日間ほどトイレが近くにないところへは遠出できなかった

宿に宿泊している他の日本人もインドに感化されのんびりしている。宿代も飯代も安い。宿にいる日本人の旅行話、身の上話も十人十色でまあ面白いこと。心細い異国の地で郷里を共にする(実際は別の都道府県出身だが)人と話していれば、同じ言葉を話すだけでも自然と心が打ち解けてしまう。カモを見つけてはすぐに騙そうとしてくる人が多いニューデリーなら尚更である。

それゆえ、この宿に泊まる人はまあまだここにいていいかと滞在する日が1日1日と伸びるそうだ。自分は出発のチケットを予め取っていたので1泊か2泊しかしなかったが、そうでなければもう少し長くいただろうと確かに思う。

とまあ、ここまでが長い前置きである。ニューデリーの話はまた別の記事で書こうと思う。この記事では、そのような居心地の良い「沈没地」を日本で見つけてしまったという話をしたいのだ。

そこは目黒区の西小山にある東京浴場という銭湯だ。 西小山駅から徒歩5分くらいの場所に位置し、スーパーのライフの向かいにあるのでわかりやすい。暖簾をくぐると大きな本棚が聳え立っており、上から下まで全部マンガで埋め尽くされている。なんと、入浴料を払うだけでこの漫画が読み放題なのだ。

https://shinagawa1010.jp/list/tokyo_b/

この銭湯は親子3世代で来れる銭湯を目指しているそうだ。 実際、小学生くらいの子供がいる親子連れ、友人と連れ立ってきている大学生くらいの若者からローカルの方と見受けられるお年寄りまで、確かに全世代で湯船を共にしていた。

しかもその比率はどの年代に偏っているわけでもない。これはすごいことだ。地域の人に愛されているというのはこういうことかと実感した。

今人気のサウナは予約が必要な個室のものが一つあるだけだ。だが、高温の湯と井戸水を汲んだ冷たい水風呂がある上、椅子が5つもあるのでゆっくり休憩することができる。熱い風呂に入り、水風呂で体を覚まし、椅子で休憩できる。そんな素晴らしいところだ。

今日は祝日だったため、昼過ぎに東京浴場に行ってみた。 風呂に入って日頃の疲れと汗を流す。露天風呂ではないものの、天井が高いため開放感がある。上がって体を拭き、座席を確保してマンガ(スパイファミリー)を読む。うとうとと眠たくなってきたら、もう一度熱い風呂に入って目を覚ます。理想の休日の過ごし方だった(滅多に再現できない)。

銭湯でリフレッシュするにあたり必須のステップは椅子に座って何も考えずにぼーっとすることだ。これを飛ばしてしまうと、あくせくとした日常の延長になってしまう気がする。

椅子での休憩中に面白い発見があった。湯船から上がって椅子に座りながらなんとなく周りをみると、銭湯で流れている時間は日常とは異なることに気づいた。時間の流れがゆっくりなのだ。そして、それは自然と人の動きが遅くなるからだと気づいた。

浴室では足を滑らせないようにしてか、屈強な体格の持ち主でも一歩一歩確かめるようにして歩いている。湯船に浸かってのぼせるくらい顔を真っ赤にした人でも、湯から上がるときは急いでいない。

休憩用の椅子に座る人は深く腰掛けようとして、座面に尻をつける直前にちょっと静止するように見える。その一瞬のうちに、椅子が冷たくても我慢するという決死の覚悟をしているのである。

その他にも、一定のスピードで湯が足されていくじゃぶじゃぶという音、湯桶がタイルに当たるかこんという音、離れた場所で人が会話しているぼそぼそという音。そんな音が蒸気で曇る室内で薄ぼんやりと聞こえてくる。目と耳で楽しんでいると、足には水風呂から流れ出てきた冷水が当たっている。

五感で銭湯を感じながら蛍光灯をぼんやりみているとその瞬間がきた。「ととのった」のだ。

自分がととのう前兆は背骨のあたりが急にズシンと重くなること。これが起きたら直後に視野は狭まり、遠くにある蛍光灯の光がはっきり見え、顔の筋肉が弛緩する。この時、話しかけられたりしてはいけない。そのまま蛍光灯を見続けると光が大きくなってくる。光への没入感が大きくなったらととのった証拠だ。

2分ほど経つとすっかり元通りになる。頻繁にサウナに行くわけではない自分がはっきりととのったのは1年ぶりだ。なんだか頭がすっきりしたように感じる。今度は今度は友人ときてみようか。ここであれば友人もととのうのだろうか。

そんなとりとめのない考えを誰かに話すわけでもなく、とりあえず note に書く決意をして、すっかり日が落ちてしまった西小山の東京浴場を後にしたのだった。

プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

現在時刻との時間差を計算する

本記事は現在時刻との時間差を計算して、「○秒前」「○日前」と相対的な日付に変換する関数の紹介です。以前 PHP で同様の関数を作成したことがありました。

参考: PHPの日付処理ライブラリCarbonで現在時刻との差(diff)を取得する

今回は、TypeScript と date-fns を用いて同様のことをします。これで日付をそのまま表示せずに済み、投稿などが新しいか時間が経っているか一目瞭然になります。

実は date-fns には formatRelative という関数があり、その結果を日本語化できます。

ただ、このままだと「1日前」と表示してほしいところで「昨日の03:00」という時刻も入った表現になって使いづらいため簡単に自作してみました。このブログの投稿一覧ページで使用しています。

テストも併せて掲載するので、仕様はテストを読んでいただくとわかりやすいです。

date.ts
import differenceInDays from 'date-fns/differenceInDays'
import differenceInHours from 'date-fns/differenceInHours'
import differenceInMinutes from 'date-fns/differenceInMinutes'
import differenceInMonths from 'date-fns/differenceInMonths'
import differenceInSeconds from 'date-fns/differenceInSeconds'
import differenceInWeeks from 'date-fns/differenceInWeeks'
import differenceInYears from 'date-fns/differenceInYears'

// 「○秒前」「○分前」と表示する
export const diffTime = (base: Date, target: Date) => {
  const diffInSecs = differenceInSeconds(base, target)
  if (diffInSecs < 60) {
    return `${diffInSecs}秒前`
  }

  const diffInMins = differenceInMinutes(base, target)
  if (diffInMins < 60) {
    return `${diffInMins}分前`
  }

  const diffInHours = differenceInHours(base, target)
  if (diffInHours < 24) {
    return `${diffInHours}時間前`
  }

  const diffInDays = differenceInDays(base, target)
  if (diffInDays < 7) {
    return `${diffInDays}日前`
  }

  const diffInWeeks = differenceInWeeks(base, target)
  if (diffInWeeks < 4) {
    return `${diffInWeeks}週間前`
  }

  const diffInMonths = differenceInMonths(base, target)
  // 4週間前でも 0ヶ月前と表示されるため、条件を足して絞り込む
  if (diffInWeeks >= 4 && diffInMonths < 2) {
    return `1ヶ月前`
  } else if (diffInMonths < 12) {
    return `${diffInMonths}ヶ月前`
  }

  const diffInYears = differenceInYears(base, target)

  return `${diffInYears}年前`
}
__tests__/date.test.ts
import subDays from 'date-fns/subDays'
import subHours from 'date-fns/subHours'
import subMinutes from 'date-fns/subMinutes'
import subMonths from 'date-fns/subMonths'
import subSeconds from 'date-fns/subSeconds'
import subWeeks from 'date-fns/subWeeks'

import { diffTime } from '../date'

describe('diffTime', () => {
  const now = new Date('2025-12-31 23:59:59')

  describe('○秒前と表示する', () => {
    test('1秒前の時、1秒前を返す', () => {
      const date = subSeconds(now, 1)
      expect(diffTime(now, date)).toBe('1秒前')
    })

    test('59秒前の時、59秒前を返す', () => {
      const date = subSeconds(now, 59)
      expect(diffTime(now, date)).toBe('59秒前')
    })
  })

  describe('○分前と表示する', () => {
    test('60秒前の時、1分前を返す', () => {
      const date = subSeconds(now, 60)
      expect(diffTime(now, date)).toBe('1分前')
    })

    test('59分前の時、59分前を返す', () => {
      const date = subMinutes(now, 59)
      expect(diffTime(now, date)).toBe('59分前')
    })
  })

  describe('○時間前と表示する', () => {
    test('60分前の時、1時間前を返す', () => {
      const date = subMinutes(now, 60)
      expect(diffTime(now, date)).toBe('1時間前')
    })

    test('61分前の時、2時間前を返す', () => {
      const date = subMinutes(now, 61)
      expect(diffTime(now, date)).toBe('1時間前')
    })

    test('23時間前の時、23時間前を返す', () => {
      const date = subHours(now, 23)
      expect(diffTime(now, date)).toBe('23時間前')
    })
  })

  describe('○日前と表示する', () => {
    test('24時間前の時、1日前を返す', () => {
      const date = subHours(now, 24)
      expect(diffTime(now, date)).toBe('1日前')
    })

    test('6日前の時、6日前を返す', () => {
      const date = subDays(now, 6)
      expect(diffTime(now, date)).toBe('6日前')
    })
  })

  describe('○週間前と表示する', () => {
    test('7日前の時、1週間前を返す', () => {
      const date = subDays(now, 7)
      expect(diffTime(now, date)).toBe('1週間前')
    })

    test('3週間前の時、3週間前を返す', () => {
      const date = subWeeks(now, 3)
      expect(diffTime(now, date)).toBe('3週間前')
    })
  })

  describe('○ヶ月前と表示する', () => {
    test('4週間の時、1ヶ月前を返す', () => {
      const date = subWeeks(now, 4)
      expect(diffTime(now, date)).toBe('1ヶ月前')
    })

    test('11ヶ月前の時、11ヶ月前を返す', () => {
      const date = subMonths(now, 11)
      expect(diffTime(now, date)).toBe('11ヶ月前')
    })
  })

  describe('○年前と表示する', () => {
    test('12ヶ月前の時、1年前を返す', () => {
      const date = subMonths(now, 12)
      expect(diffTime(now, date)).toBe('1年前')
    })

    test('13ヶ月前の時、1年前を返す', () => {
      const date = subMonths(now, 13)
      expect(diffTime(now, date)).toBe('1年前')
    })

    test('23ヶ月前の時、1年前を返す', () => {
      const date = subMonths(now, 23)
      expect(diffTime(now, date)).toBe('1年前')
    })

    test('24ヶ月前の時、2年前を返す', () => {
      const date = subMonths(now, 24)
      expect(diffTime(now, date)).toBe('2年前')
    })

    test('25ヶ月前の時、2年前を返す', () => {
      const date = subMonths(now, 25)
      expect(diffTime(now, date)).toBe('2年前')
    })
  })
})
TypeScript
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer