3月の朝、ベランダの手すりにはまだ薄く結露が残っていました。私は鉢に残った土を見つめながら、スマートフォンに届いたメッセージをもう三回読み返していました。
「届いたの、トマトの苗じゃなくて、多肉植物の子株だったよ。これはこれで可愛いけど……頼んでたのと違うよね?」
近所のベランダ菜園仲間との、小さなお裾分けの輪。注文を取りまとめる係を買って出たのは私です。そして、その注文を記録するための小さなツールを、覚えたてのRustで自作したのも私です。
半年前、会社の経理部で、入出金チェックの検証スクリプトをシステム担当がRustで書き直したことがありました。「型に厳しい言語のほうが、伝票の桁間違いに気づきやすいんですよ」。あのときは「へえ、便利そうだな」くらいにしか思っていませんでした。今、自分のコードで型はちゃんと合っているはずなのに、実害が出ている——あの説明が指していたものの輪郭が、まだ掴めずにいます。
経理の仕事なら、こういうときはまず伝票を見返します。今回も同じはずです。ただし伝票の代わりに、自分の書いたコードを見返さなければなりません。菜園仲間の一人が教えてくれた住所——「コードの相談に乗ってくれる庭師さんがいるらしいよ」——を、スマートフォンのメモから開きました。
訪れ ── 届いたのは、頼んだはずの苗じゃなかった
住宅街の外れ、駅前の坂道を下っていくと、小さな工房と庭が見えてきます。入口の脇に、一本の古い木が立っていました。枝ぶりが妙に一方向にだけ長く伸びているな、と一瞬思いましたが、今日はそれどころではありません。会釈もそこそこに、木の脇を通り過ぎて工房の戸を叩きました。
戸が開き、庭師さんが黙って顔を出しました。歳の頃合いも性別も、正直よく分かりません。ただ、土のついた指先だけが、長年の仕事を物語っています。工房の中は、乾いた土と、青い葉の匂いが混じっていました。作業台の上には、剪定鋏や麻紐、小さな鉢がいくつも並んでいます。会社の経理部の、書類とコーヒーの匂いに慣れた鼻には、少し落ち着かない匂いでした。
「あの、コードの相談で……」
私が言い終わる前に、庭師さんは小さく頷いて、奥の作業台を手で示しました。見習いらしい若い女性が、庭師さんの後ろから顔を出して「いらっしゃい、どうぞ座ってください」と笑いかけてくれます——この人がヒバリさんか、と思いました。私は席に着き、ノートパソコンを開きます。
「最近元気がなくて……いえ、正確には、元気がないのは私のコードのほうなんですけど」
庭師さんが、短く言いました。「見せて」
自分でも変な言い方だと思いながら、私は今朝届いたメッセージと、自分が書いたコードの呼び出し箇所を画面に出しました。
| |
症状観察 ── 庭師は、何も言わずに数字をなぞる
作業台の窓からは、庭の一角がちらりと見えました。鉢植えがいくつも並び、まだ朝露の残る葉が、弱い光を受けて光っています。庭師さんは椅子に座り直すこともせず、私のノートパソコンの画面を覗き込む格好のまま、しばらく動きませんでした。
庭師さんは、私が開いた画面に視線を落としました。何も言いません。指先で画面の縁を軽くなぞるように動かし、3と15という2つの数字の並びのあたりで、視線の動きが止まりました。
私はその沈黙に落ち着かなくなって、「あの、何かおかしいところ、ありますか」と聞いてしまいます。庭師さんは、答えません。ただ、pot_size: u32とwatering_frequency: u32という2行を、もう一度指でなぞりました。
経理の仕事なら、数字が合わないときは伝票と現物を並べて照合します。庭師さんが今やっているのは、たぶんそれに近いことなんだろう、と私は思いました。ただ、何と何を照合しているのかが、私にはまだ分かりません。
私は、庭師さんの視線が同じ場所を行き来しているのに気づきます。
「型は……両方u32で、合ってると思うんですけど」
庭師さんの手が、ふっと止まりました——さっきまでのなぞるような動きが、止まります。ヒバリさんが横から、庭師さんの様子をちらりと見て、何かを察したような顔をしました。私はまだ、庭師さんが何を見ているのか分かりません。ただ、「型が合っている」ということが、今回はむしろ問題の一部なのかもしれない、という予感だけが、うっすらと胸に残ります。
「これ、いつ書いたコードですか」
庭師さんが、初めて短く口を開きました。
「えっと……最初は3ヶ月前です。品種名と鉢サイズだけの、2つの引数で始めました」
庭師さんは、また頷くだけで、何も言いません。私は続けて説明します。
「日照条件と、水やり頻度は、後から仲間の要望で足しました」
庭師さんは、私の説明を聞きながら、コンストラクタの引数リストにもう一度目を落としました。
| |
見立てと翻訳 ── 「詰め合わせ種袋」という名前
庭師さんが、ヒバリさんに向かって、短く一言だけ告げました。
「詰め合わせ種袋だな」
ヒバリさんが、私の方を向いて、噛み砕いて説明を始めます。
「師匠が"詰め合わせ種袋"って言うのは、つまり——種袋に、品種も鉢サイズも日照条件も水やり頻度も、全部まとめてぎゅっと詰め込んで、順番通りに取り出さないと中身が分からなくなる、っていう状態のことなんです」
私は、思わず自分のコードを見返しました。「順番通りに……あ」
ヒバリさんが続けます。「今回のコードだと、pot_sizeとwatering_frequency、どっちもu32型ですよね。順番を一つ入れ替えても、Rustのコンパイラは『型が合っているか』しか見てくれないので、意味の入れ替わりには気づけないんです。ちなみにsunlightのほうはenum(いくつかの決まった選択肢から一つを選ぶための型。今回は日なた・半日陰・日陰の3つ)なので、こっちは他の項目と型が違って、取り違えても気づけます。今回問題になったのは、たまたま型が一致してしまったpot_sizeとwatering_frequencyの組み合わせなんです」
私は頷きながら、自分が実際に書いた呼び出しをもう一度見つめました。
「私、3をwatering_frequencyのつもりで、15をpot_sizeのつもりで書いたんですけど……実際にはpot_sizeのところに3が入って、watering_frequencyのところに15が入っちゃったんですね」
「そうなんです。しかも困ったことに」とヒバリさんが続けます。「15って値、単体で見ると全然おかしくないんですよ。『15日おきに水やり』って、多肉植物とかならごく普通の間隔です。だから、コードを読み返しても、パッと見て違和感に気づきにくいんです」
言われてみればその通りでした。私が今朝、この呼び出しを何度も見返したのに気づけなかったのは、値そのものが「あり得ない数字」ではなかったからです。もしwatering_frequencyに-1のような明らかにおかしい値が入っていたら、すぐに気づけたはずでした。
庭師さんが、ぽつりと言いました。「同じ形をしてるとな」
私は少し俯いて、経緯を語ります。
「最初は品種名と鉢サイズの2つだけで、本当にそれで十分だったんです。仲間内でも『鉢のサイズさえ合ってればいいよ』って感じで。それが、だんだん『うちは西日きついから日照条件も知りたい』とか、『水やりの手間も知りたい』とか、要望が増えて……そのたびに、引数を後ろに一個ずつ足していきました」
ヒバリさんは、うんうんと頷きます。「最初はそれで足りてたんですよね。誰も悪くないですよ、それ」
庭師さんも、小さく頷きました。
「で、結局何が届いちゃったんですか」とヒバリさんが聞きます。私は、今朝のメッセージを見せました。
「トマトの苗を頼んだ仲間のところに、多肉植物の子株が届いたんです」
ヒバリさんは少し驚いた顔をして、「え、品種名は"トマト"のままですよね? なんで違う植物が……」と聞き返します。
「品種名はコードの中では正しく"トマト"のままなんです」と私は説明しました。「でも、実際にお裾分けを発送するとき、鉢サイズごとに苗を置いてある棚が分かれてて……1〜5cmの種トレイ棚、6〜12cmの標準鉢棚、13〜20cmの大鉢棚っていう3段の棚です。鉢サイズが3だと、種トレイ用の小さな棚から用意することになるんです。私は15のつもりだったのに、記録上は3になってたから、梱包担当の仲間は種トレイ棚を見て、そこに一緒に置いてあった多肉植物の子株を、間違えてトマトだと思って発送しちゃったんです」
私は、自分の手帳を取り出しました。経理の仕事では、数字が合わないときに、伝票の流れを一段ずつ書き出して、どこで食い違ったかを追います。今回も、同じことをしてみようと思いました。
「私が思ってた鉢サイズと水やり頻度」「実際にコードに記録された値」「その値で選ばれた棚」「実際に届いた苗」——手帳に、四つの箱を上から順に書いて、矢印でつないでいきます。書きながら、あることに気づきました。二段目の「コードに記録された値」のところに、私は思わず「コンパイル通過・実行時エラーなし」と書き添えていました。どこにもエラーが出ていないのに、四段目では、頼んだはずのトマトが多肉植物にすり替わっている——手帳の上でも、その落差だけがやけにはっきりしていました。

庭師さんが、初めて少しだけ表情を動かしたように見えました。「品種名は、無事だな」
「コンパイルは通って、実行時のエラーも出ない。ただ、棚を間違えただけ、なんですよね」
私がそう付け加えると、庭師さんは短く「ああ」と答えました。
少し考えて、私は思いついたことを口にしてみました。
「じゃあ、pot_sizeとwatering_frequencyを、別々の型にしちゃえばいいんじゃないですか? たとえばPotSizeっていう専用の型と、WateringFrequencyっていう専用の型を作って、u32のまま並べないようにするとか」
会社の経理システムで、金額の単位を間違えないように専用の型を作った話を思い出しながら聞いたつもりでした。ヒバリさんは、少し感心したような顔をします。
「いい発想です。それ、newtypeっていう手法で、実際できます。PotSize(u32)、WateringFrequency(u32)って、それぞれ専用の入れ物を作れば、この2つを入れ替えることはコンパイルエラーで防げます」
「じゃあ、pot_sizeとwatering_frequencyの2つだけ、newtypeにすればいいじゃないですか。今回ぶつかったのはその2つだけなんですし」
「それでも、今回の取り違えは防げます」とヒバリさんは頷きました。「ただ、今後また項目が増えたときのことを考えてみてください。たとえばfertilizer_amount(施肥量)みたいな、u32の新しい項目が増えたとします。その項目がpot_sizeかwatering_frequencyのどちらかと隣り合ったら、また同じことが起きます。そのたびに『あ、今回も専用の型を作らなきゃ』って思い出せればいいんですが、思い出せなかったら、また同じ事故です。専用の型を作るかどうかの判断が、毎回、書く人の記憶に懸かってしまうんです」
私は、少し想像してみました。確かに、項目が増えるたびに専用の型も増えていくというのは、今回の悩みの種——引数が増えるたびに書き足してきた、という経緯そのものを繰り返しているようにも思えます。
「じゃあ、専用の型を作らないで、呼び出すときに毎回、項目名を書いて渡す書き方——Seedling { name: ..., pot_size: 15, ... }みたいな——じゃダメなんですか」と私はさらに聞いてみました。
「それも一つの手です」とヒバリさんは答えます。「ただ、それだと注文を書くたびに、4項目全部を毎回書かないといけません。ほとんどの注文は標準的な設定で足りるのに、それでも4つ全部を書く手間は減らせないんです。項目がこれから5個、6個と増えたら、そのたびに全部の呼び出し箇所を書き直すことになります」
「もう一つ、聞いてもいいですか」と私は続けました。「Seedlingの値を作るときに、既定値の構造体をコピーして、必要なところだけ書き換える、みたいなやり方もあるって聞いたことがあるんですけど」
ヒバリさんが頷きます。「..Default::default()みたいな書き方ですね。それも候補にはなるんですけど、Rustの決まりで、この書き方を使うには構造体の全部の項目が外から見える状態——pub——になってないといけないんです」
「全部……ですか」
「はい。でも」とヒバリさんは、私のノートパソコンの画面を指しました。「さっき伺った感じだと、これから鉢サイズの妥当性チェック——たとえば大きすぎたり小さすぎたりする値を弾く、みたいなことも考えてらっしゃいますよね?」
私は驚いて頷きました。「あ、はい。今はまだ何もチェックしてないですけど、いずれは」
「だとすると、項目を全部外から見える状態にしちゃうのは、都合が悪いんです。中身を勝手に書き換えられないようにしておいて、決められた入り口からしか値を作れないようにしたい——そうなると、この書き方は使えません」
庭師さんが、初めて自分から付け加えました。「どっちも、悪くはない」
私は、その言い方に少し意外な気がしました。「悪くない、けど……」
「今回には、合わない」とヒバリさんが引き取ります。「newtypeは、専用の型を作り忘れたら効果が無い——書く人の記憶に懸かってます。項目名を書いて渡す書き方は、毎回全項目を書く手間が減らない。構造体のコピーは、これから足したい中身のチェックと相性が悪い。今回みたいに、項目が増え続けていて、後から中身のチェックも足したい、っていう状況には、一つずつ名前をつけて積んでいくやり方のほうが合ってるんです」
手入れ ── 名前をつけて、一つずつ積む
庭師さんが、ノートパソコンのキーボードに手を伸ばしました。「積む」——庭師さんは、それだけ言うと、私が場所を譲った先で新しい関数をいくつか書き始めます。ヒバリさんが横から実況してくれました。
「師匠がやってるのは、注文の項目を、順番で並べる代わりに、ひとつずつ名前をつけて積んでいくやり方です。『受注書』みたいなものですね」
庭師さんの手が、SeedlingBuilderという構造体を書き上げ、pot_size・sunlight・watering_frequencyという3つの短いメソッドを、テンポよく並べていきます。ヒバリさんが手元を見ながら言いました。「農事暦の定石でいうBuilder(一度に全部を指定するのではなく、必要な設定を一つずつ積み上げてから、最後に一度だけ完成させる、という定石)ですね」。
「あの、元のSeedling::newは、どうなるんですか」と私は聞きました。位置引数のままのコンストラクタが、どこかに残ってしまうんじゃないか、と気になったからです。
「消します」とヒバリさんは、あっさり答えました。「Seedlingを作る入り口は、SeedlingBuilder経由の一本だけにします。元のnewを残したままだと、うっかりそっちを呼んでしまう人が、また同じ取り違えをやり直すことになりますから」
| |
庭師さんは書き終えたコードから手を離すと、作業台の隅にあった木札を二枚、無言で並べました。一枚には四つの数字がそのまま書きつけてあり、もう一枚には麻紐で一つずつ結わえられた小さな荷札が、順番に連なっています。
「これが、Before と After です」とヒバリさんが、庭師さんの手元を指しながら言いました。「左は、値がただ順番に並んでるだけの種袋。右は、荷札一枚ごとに名前がついてて、順番に結んである受注書。見た目からして、もう違いますよね」
私は、自分が書いた元のコードと、書き直してもらったコードを、木札の絵と見比べました。左の木札は、確かに、どれが鉢サイズでどれが水やり頻度なのか、絵だけを見ても分かりません。右の木札は、荷札の文字を読めば済みます。

「まず、必須なのは品種名だけです」とヒバリさんが説明します。「SeedlingBuilder::new("トマト".to_string())——これだけで、ひとまず受注書が始まります。鉢サイズも日照条件も水やり頻度も、まだ何も指定してません」
私は聞き返しました。「指定しなかったら、どうなるんですか」
ヒバリさんは、new関数の中身を指さします。「既定値が入ります。標準鉢の9cm、半日陰、7日おき。うちの仲間内でいちばん多い注文のパターンに合わせてあるみたいですね」
庭師さんが、初めて口を開きました。「大体は、これで足りる」
私は、さっき話していた「これから鉢サイズの妥当性チェックを足したい」という話を思い出しました。「今のこのbuild、まだ何もチェックしてないですよね。チェックを足すとしたら、どこに書くんですか」
「ここです」とヒバリさんは、build関数を指しました。「今はまだ、フィールドをそのまま渡してるだけです。チェックを足したくなったら、この一箇所を書き換えるだけで済みます。Seedlingを作る入り口が、buildのこの一箇所に集まってるからです」
「じゃあ、鉢サイズと水やり頻度、両方指定したいときは?」と私が聞くと、ヒバリさんが実際のコードを画面に出しました。
| |
ヒバリさんが続けます。「こう書きます。.pot_size(15)って書いたら、それはもう見た瞬間に鉢サイズだって分かりますよね。.watering_frequency(3)も同じです。数字だけが裸で並んでる状態から、名前がついた状態に変わります」
私は、自分が書いた元のコードと見比べました。「あ……確かに、元のコードだと3と15って数字が裸で並んでるだけで、どっちがどっちか、コードを読む側は覚えておかないといけないですもんね」
私は、少し気になっていたことを聞いてみました。
「これで、もう順番を間違えることは無くなるんですか?」
庭師さんは、少し間を置いてから答えました。
「無くならない」
私は、拍子抜けした顔をしたと思います。ヒバリさんが補足してくれました。
「.pot_size(2)って書くところを.watering_frequency(30)って書き間違えることは、まだできちゃうんです。builderは、間違いそのものを止めてはくれません。変わるのは、それぞれの値に名前がついて、書いた場所を見ればどっちの設定か分かるようになる、ってことです。レビューする人が見ても、pot_sizeって書いてあれば鉢サイズの話だってすぐ分かる。今までみたいに、数字を4つ並べて、順番を頭の中で数えなくて済むんです」
私は、その正直な言い方に、かえって納得しました。「なるほど……魔法みたいに全部解決するわけじゃないんですね」
ヒバリさんが、もう一つ付け加えます。「それに、既定値があるので、普段の注文だとpot_sizeをわざわざ書かないことも多いんです。標準鉢でよければ.build()だけで済みますから。両方の数字を同時に書く場面自体が、前より減るんです。取り違えられる場面そのものが、少なくなる感じですね」
「あ、そうか。書く回数が減れば、間違える回数も減りますもんね」
少し腑に落ちた気がしました。
私はさらに聞きます。「さっき見た書き方、selfを受け取ってselfを返す、みたいになってましたけど、これって何か意味があるんですか」
庭師さんが、build関数の中身を指で示しました。ヒバリさんが翻訳します。
「これ、大事なところです。buildの最後は、SeedlingBuilderが持ってる品種名や鉢サイズを、そのまま新しいSeedlingに渡してるんです——コピーじゃなくて、渡し切ってる。Rustでは、所有権(値の持ち主は常に一つに決まる、という考え方。所有権が移ると、元の変数はもう使えなくなる)という決まりがあって、渡した瞬間、元のSeedlingBuilder側はもう使えなくなります。庭師さんたちの言い方だと、『水やり当番は常に一人』ってやつですね」
私は、少し噛みしめるように頷きました。「一時的に借りて、途中はコピーで済ませる作り方もあったりするんですか」
ヒバリさんが頷きます。「あります。途中は借用(値を渡さずに、一時的に見たり触ったりするだけの参照。借りている間は複数箇所から見られるが、渡す=消費することはできない)だけにして、最後だけコピーして渡す作りも書けます。でも、コピーの分だけ手間が増えるので、今回は最初から『渡して、また受け取る』——渡し切る作りで途中も揃えてあるんです。途中を借りるだけにしてしまうと、最後のbuild()で渡し切れなくなるので、揃え方を合わせてあります」
私は、頭の中で少し整理してみました。値を一時的に見るだけの状態は借用と呼ぶのだと、以前会社のシステム担当から聞いたことがあります。借りているだけなら、渡すことはできない——一時的に世話を任される、という状態。だから、pot_sizeやsunlightのようなメソッドも、借りるのではなく、毎回きちんと渡して、また受け取る形に揃えてあるのだと分かりました。
見送り ── 帰り道、木の枝ぶりが目に留まる
私は、書き直したコードを自分のノートパソコンに書き写しながら、庭師さんに聞きました。
「今日届いた注文、もう一度ちゃんと組み直せますよね」
庭師さんは頷きます。「ああ」
ヒバリさんが「今度、仲間の方に謝るときは、コードの中身まで説明しなくても大丈夫ですよ。『順番を間違えてました、直しました』でじゅうぶんです」と笑いました。私は、少しほっとして、荷物をまとめ始めました。
戸口まで見送りに出た庭師さんが、最後に一言だけ添えました。「また来るといい」
帰り際、私は工房を出て、来た道を戻ります。入口の脇の、あの古い木の前を通りかかったとき——今度は、なぜか目が留まりました。枝ぶりが一方向に偏って伸びているのが、来たときよりもはっきり見える気がします。何か理由があるんだろうか、と一瞬思いましたが、聞くほどのことでもない気がして、そのまま通り過ぎました。ただ、行きに見たときと、帰りに見たときとで、同じ木のはずなのに、目に映るものが違って見えたのが、少し不思議でした。朝は弱かった光が、いつの間にか少し強くなっていました。
帰りの坂道を上りながら、私は独りごちます。
「順番で覚えるんじゃなくて、名前で確かめる、か」
今日の注文記録を直したら、次は仲間内のグループチャットに一言添えて謝ろう、と思いました。そして、いつかまた、項目が増えることがあったら——今度は、慌てて末尾に付け足す前に、少し立ち止まって考えよう、と思いました。
手入れ記録
| こんな症状が出たら | 入れるべき手入れ(パターン) | まだ様子見でいい |
|---|---|---|
| コンストラクタの引数が4つ以上あり、同じ型の項目が隣接している | ✓ Builder(必須項目+デフォルト値+チェーン) | |
| 項目は2〜3個で、今後も増える予定が無い | ✓ そのままの位置引数で様子見 | |
| 構築後に値の妥当性チェックを追加したい予定がある | ✓ Builder(非公開フィールド+build()での検証余地) |
手入れの手順
- コンストラクタの引数を数え、同じ型が隣接している箇所を洗い出す
- 本当に必須のデータは何か(無いと構築できないもの)を1つに絞る
- 残りの項目にはよく使われる既定値を与え、各項目を1つずつ設定するメソッドに分解する(
mut self -> Selfで統一し、値を渡し切る) - 終端メソッド(
build)で、各フィールドの所有権をそのまま最終的な構造体へ渡す - 元の位置引数コンストラクタは削除するか非公開にし、既存の呼び出し箇所を名前付きのメソッドチェーンに置き換える
庭師の一言
数字は転がる。名は、根を張ったまま動かない。
見習いの手控え
師匠の言葉、要は「順番で覚えるより、名前で確かめたほうがいい」ってことです。今日は、実際に届いた物が違った、っていう分かりやすいきっかけがあったから、気づけましたね。次に自分のコードで似た形を見たときは、届く前に気づけるといいですね。
