8月上旬、朝から強い日差しが照りつける。公園管理事務所の駐輪場から、点検用の電動カートに乗り込む。今日は市内3か所の花壇の一斉点検日で、巡回ルートの最後に庭師の工房が入っている——特別な寄り道という意識はなく、ルーティンの延長線上として立ち寄るつもりだった。カートのモーター音に混じって、もう蝉の声が本格的に響き始めている。
私は29歳、市役所の公園緑地課に勤務して6年目になる。市内の公園・街路樹・花壇の維持管理を担当する係の中堅で、夏は一斉点検・除草・水やりの繁忙期だ。3年前、花壇ごとの水やり記録・剪定記録を紙の点検表からデジタル化する話が持ち上がった際、予算の都合で外部発注できず、独学でRustを覚えて自分でツールを書くことになった。当時は「動けばいい」くらいの気持ちで書いた小さなスクリプトだったが、今では花壇の登録・水やり間隔の管理・点検記録の出力を行う内製ツールとして、業務の合間に自分ひとりでメンテし続けている。
去年の今頃も、同じように汗だくで巡回ルートを回っていた気がする。1年経っても、点検表の様式もツールの骨格も、ほとんど変わっていない——変わったのは、扱う花壇の数だけだ。
訪れ ── 花壇と寄せ植え鉢、型が足りない
カートを走らせながら、今朝方の作業を思い出す。中央公園花壇に、来園者向けの寄せ植え鉢を3つ設置する準備をしていた。管理ツールに登録しようとして、手が止まった。
私が普段使っているツールは、こんな形をしている。
| |
個別の株にはwater_plant、花壇まるごとにはwater_bed。花壇が2つだけの頃は、正直そこまで困っていなかった。花壇が増えても、同じ2種類の関数を花壇の数だけ呼べば済んでいたからだ。
ところが今回、寄せ植え鉢という"その中間の単位"をどう登録すればいいのか、分からなかった。寄せ植え鉢自体も複数の株のまとまりで、Plant単体ではない。かといってBedのplants: Vec<Plant>には、Plantしか入れられない。花壇の中に、もう一段小さいまとまりを入れる余地が、最初から無かった。
タブレットの画面をしばらく睨んで、PottedArrangementという名前の構造体を書きかけては消す、を何度か繰り返した。仮に書けたとしても、今度はBedがPlantとPottedArrangementのどちらも持てるようにしなければならない。結局、また別の分岐が増えるだけではないか——そこまで考えて、朝の作業を一旦保留にした。この状態のまま点検に出るのは気が重かったが、他の花壇も回らなければならない時間だった。
工房の前で電動カートを停める。入口の脇に古い木が一本あるのが目に入るが、点検の段取りで頭がいっぱいで、特に立ち止まらずに戸口へ向かう。中から先にヒバリさんが顔を出し、「あ、点検お疲れさまです」と声をかけてくる。少し遅れて庭師も奥から現れたが、何も言わずに小さく会釈しただけだった。以前にも顔を合わせたことがあるらしい。工房の中は、外の熱気とは対照的にひんやりとしていて、水を打ったばかりらしい土の匂いがした。
「花壇の管理コードで、ちょっと詰まってて」とタブレットの画面を見せる。「個別の株用と、花壇全体用で、別々の関数を使い分けてるんですけど、今度その中間くらいの単位——寄せ植え鉢——を扱いたくて。どう書けばいいのか、行き詰まってます」。
庭師は何も言わず、画面に目を落とした。
症状観察 ── 手のひらが、地面から浮く
庭師は、water_plant関数とwater_bed関数を交互に見比べる。しばらく無言のまま、Bed構造体のplants: Vec<Plant>というフィールド定義に視線を留めた。
私は沈黙に耐えかねて「型を増やせばいいだけかと思ったんですけど、そうすると余計ややこしくなりそうで」と言ってみる。庭師は答えない。しゃがんで足元の土に触れていた手のひらが、地面からわずかに浮いた——それが何を意味するのか、私にはまだ分からない。
庭師は、画面上でwater_plantとwater_bedの中身を指先でなぞる。どちらも、最終的にやっていることは「名前と水やり間隔を文字列にまとめる」という、ほとんど同じ処理だった。Bedの方は、それをVecでループして集めているだけの違いしかない。庭師の視線は、その"ほとんど同じなのに、型と関数だけが分かれている"様子を確かめるように動いていた。
作業台の向こうで、ヒバリさんも一緒に画面をのぞき込んでいたが、口は挟まなかった。庭師が観察している間は、誰も割り込まない——そういう空気なのだと、この短いやり取りだけで察しがついた。私も倣って黙る。工房の外から、次の巡回先の時間が少しずつ気になり始めていたが、口には出さなかった。
見立てと翻訳 ── 「同じことを、二つの窓口でやっている」
庭師がぽつりと言う。
「同じことを、二つの窓口でやっている」
私は「えっと、株ごとと、まとまりごとで、なんか……」と言いかけて言葉に詰まる。ヒバリさんが横から引き取って言い切った。
「呼び出し方が、割れちゃってるんですよね」
「ああ、それです、それ」と私は思わず声を強める。ヒバリさんが続ける。「Plantを手入れするのも、Bedを手入れするのも、突き詰めれば『手入れする』という同じ操作のはずなんです。でも今のコードだと、対象が単体かまとまりかで、呼ぶ関数自体が変わっちゃってます」。
「これ、Composite(コンポジット)っていう定石があります」とヒバリさんが名前を出す。「個別の要素と、その集合を、同じ手続きで扱えるようにする定石です。一本の株も、束ねた株の集まりも、同じ手入れの手順で触れられるようにするんです」。
私は「その仕組みなら、寄せ植え鉢も同じように扱えそうですね」と先を読む。
「ちなみに、この二重管理自体は前から気づいてたんですけど」と経緯を話す。「花壇が2つだけの頃は、正直そこまで困ってなかったんです。花壇が増えても、同じ2種類の関数を、花壇の数だけ呼べばよかったので」。
「そうですよね、最初はそれで十分だったはずです」とヒバリさんが頷く。庭師も小さく頷いた。
「じゃあ、寄せ植え鉢は具体的にどう位置づければいいですか」と私は結論を急いだ。
庭師が短く答える。
「まとまりの中に、また、まとまりを入れられればいい」
私は「え、花壇の中に、また花壇みたいなものを入れる、ってことですか」と聞き返す。ヒバリさんが頷く。「はい。寄せ植え鉢も、花壇も、“複数の株をまとめて手入れする"という点では同じ形なんです」。
「まとめて手入れする、という意味では確かに同じですね……」と私はつぶやきながら、頭の中で自分のコードを思い浮かべる。花壇の一覧、花壇ごとの株の一覧、そして今度追加したい寄せ植え鉢の一覧——3種類の似たようなリストを、これまで別々に管理しようとしていた。もし本当に同じ形として扱えるなら、その3つを分けて考える必要自体が無くなるのではないか、という予感がよぎった。
手入れ ── まとまりの中に、また、まとまりを
庭師がタブレットを引き寄せ、まずCareというトレイトを書いた。ヒバリさんが実況する。
「トレイトは、複数の型に共通の振る舞いを約束させる仕組みです——前にも触れましたよね。Careは『手入れできること』を約束するトレイトで、中身はtend()——手入れした記録を返す、というメソッド1つだけです」。
| |
続けて庭師は、PlantにこのCareを実装した。
| |
「個別の株は、自分の名前と水やり間隔を、そのまま記録として返すだけです」とヒバリさんが言う。
庭師がBed構造体を書き直す。フィールドが、Plantのリストではなく変わっていた。
| |
タブレットの画面に、書き直したばかりのCareとPlant、Bedが並ぶ。私は思わず、さっきまでのBeforeのコードと頭の中で並べてみたくなった。

私は「あ、これならPlantだけじゃなくて……」と気づきかけて言葉が止まる。ヒバリさんが横から引き取る。
「Bed自身もCareを実装するので、Bedの中に別のBedを入れられるんです」
私は「でも、Vecって、中身の型は揃えないといけないんじゃ」と聞く。ヒバリさんが頷く。「はい。ただ、membersが並べているのはPlantという具体的な型そのものじゃなくて、Box<dyn Care>——『Careを実装している何か』という約束です。Bedは、相手がPlantかBedかをもう気にしていません。Careを実装しているかどうか、それだけを見ています」。私は「型そのものじゃなくて、約束の方を見てるってことですね」と繰り返す。
私は「じゃあ、寄せ植え鉢は、新しい型を作らずに、Bedをそのまま使えばいいってことですか」と聞く。庭師が頷いた。ヒバリさんが補足する。「はい。ラベルを『寄せ植え鉢』とかにして、中身にパンジーやビオラのPlantを追加するだけです。型としては、花壇と寄せ植え鉢はまったく同じBedです」。
私はふと思いつく。「だったら、その寄せ植え鉢を、そのまま花壇のmembersに追加すればいいんですね」。
庭師が短く「そう」と答えた。「Careを実装した型は、PlantとBedとで大きさが違う。だから、そのままではVecに並べられない。Boxで包んで、同じ大きさの箱に揃えている」とヒバリさんが一言だけ補う。
| |
私はcentral_park.tend()を実行する前に、この一回の呼び出しの中で何が起きているのか、順番を目で追ってみたくなった。

ヒバリさんが画面を指して言う。「central_parkが自分の子にtend()を配って回って、その子がまたBedだったら、同じことをもう一段下に配ってるだけなんです」。私は「本当に、上から下まで、同じ呼び方しかしてない……」とつぶやく。
central_park.tend()を呼ぶと、花壇のラベル→マリーゴールドの記録→寄せ植え鉢のラベル→パンジー・ビオラの記録、という順で、階層をたどりながらログが集まってくる。既存のPlantのtend()ロジックには、一切触れていない。花壇や寄せ植え鉢を組み立てる側はaddの呼び出しが増えるが、それを一括で手入れするtend()側は、対象が単体かまとまりかで分岐を書く必要が無くなった。
私はその出力を、頭の中で点検表の並びに重ねてみる。今までなら、花壇の分と寄せ植え鉢の分を別々の帳票として出力し、後で手作業で突き合わせていたはずだ。それが、tend()を1回呼ぶだけで、階層ごと1本のログにまとまって出てくる——点検の現場作業まで変わりそうだ、という実感が湧いた。
「でも、PottedArrangementみたいに、寄せ植え鉢専用の型を作った方が、コードを読んだときに意味が分かりやすくないですか」と私は踏みとどまって聞いてみる。
ヒバリさんが答える。「もしそうすると、BedがPlantとPottedArrangementの両方を子に持てるようにする必要が出てきます。そうするとまた、『どっちを受け取るか』の分岐が戻ってきちゃうんです」。
私は「あ……それだと、結局さっきのBeforeと同じ形に戻る」と自分で気づく。
庭師が一言添えた。
「名前が違っても、やることは同じなら、型も同じでいい」
「じゃあ、Bedに子を追加する仕組みは、Careトレイトの方に書くんですか」と私は聞く。庭師が首を横に振った。ヒバリさんが説明する。
「addメソッドは、Bedという具体的な型のメソッドとしてだけ用意します。もしCareトレイト自体にaddを含めてしまうと、末端のPlantにも『子を追加する』という、本来意味の無い操作を実装しなきゃいけなくなるんです」。
私は「Plantに子を追加、って確かに変ですね」と納得する。「呼ばれても何もできず、エラーにするしかなくなりますし」。
品種の数だけ並んだPlantと、それを束ねるBedをあらためて見返す。個別も、まとまりも、同じtend()という一つの入口を通っている——それだけで、私の中で二重管理という感覚そのものが薄れていくのが分かった。
「これ、前に聞いた剪定計画の話(品種ごとの手入れ方法を切り替える定石)と、似た仕組みなんですか」とふと聞いてみる。どちらも「同じ入口で色々な処理をする」という点が似ている気がしたからだ。
ヒバリさんが少し考えてから答える。「似てるところもありますけど、根っこは違います。その話は、Vec<String>という同じ戻り値の中身が、品種ごとに切り替わるだけでした——同じ操作の"やり方"を交換する話です。今回は違います。単体を渡されても、まとまりを渡されても、同じtend()という入口で、階層をたどりながら再帰的に処理が進みます。central_park.tend()を呼んだ時、内部では花壇→寄せ植え鉢→個別株、という順に処理が委譲されていってますよね。中身を切り替えてるんじゃなくて、単体かまとまりかという"構造そのもの"を、同じ扱い方に揃えてるんです」。
私は自分のBeforeコードを思い出した。「water_plantとwater_bedを呼び分けてたのが、まさにその"構造の違い"をコード側に押し付けてたんですね」。
見送り ── 「一本の枝も、束ねた枝も——同じ鋏」
私は書き直したコードをタブレットに書き写しながら、庭師に聞いた。「これで、寄せ植え鉢を増やしても、花壇側のコードは触らずに済みますよね」。
庭師は頷く。「ああ」。
ヒバリさんが「今日はComposite、初登場でしたね」と少し楽しそうに言う。
戸口まで見送りに出た庭師が、最後に一言だけ添えた。
「一本の枝も、束ねた枝も——同じ鋏」
短い言葉だったが、耳に残る響きだった。私はそれを口の中で小さく繰り返しながら、電動カートの方へ向かった。
工房を出て、電動カートに戻る前、入口の脇のあの古い木の前をふと振り返る。「そういえば、この木っていつからあるんですか」と、前にも同じことを聞いた気がしながら尋ねてみる。
庭師は短く「さあ」とだけ答え、それ以上は続かなかった。
すると、庭師の後ろに立っていたヒバリさんが、少し声を落として付け加えた。
「実は、師匠も全部は知らないらしいんです」
庭師はその発言を否定も肯定もせず、ただ土に視線を戻していた。
電動カートを走らせながら、私は独りごちる。「一本も、束ねても、同じ手で、か」。次の巡回先へ向かう道すがら、来園者向けの寄せ植え鉢を、今度は迷わず既存の花壇の中に追加できることを思い、少しだけ気が軽くなった。
日差しはまだ強く、蝉の声も途切れない。それでも工房を出るときの方が、来たときより幾分か涼しく感じられるのは、気のせいだけではない気がした。カートの荷台には、まだ午後の巡回分の点検表が控えている。それでも、今日はひとつ、確かに片付いた。
そして、さっきのヒバリさんの一言——庭師本人も知らないかもしれない、という話——が、なぜか頭の片隅に引っかかり続けていた。あの木の由来を知っているのは、いったい誰なのだろう。次にこの工房を訪れる機会があれば、また同じ問いを重ねてしまうかもしれない、と思いながら、私はアクセルを踏み込んだ。
手入れ記録
| こんな症状が出たら | 入れるべき手入れ(パターン) | まだ様子見でいい |
|---|---|---|
| 個別の対象とまとまりを別の型・別の関数で扱っていて、呼び出し方が毎回分岐する | ✓ Composite(共通トレイトを個別・まとまりの両方に実装) | |
| まとまりの中に、さらに小さなまとまりを入れたい(入れ子)が、既存の型では表現できない | ✓ Composite(まとまり自身が自分と同じ型を子に持てるようにする) | |
| 個別とまとまりで、本当に振る舞いが異なる(まとめて扱う意味がない) | ✓ 型を分けたままにする |
手入れの手順
- 個別要素用の処理とまとまり用の処理を洗い出し、実際にやっていることがどこまで同じかを確認する
- 共通の操作をトレイトとして定義する(子要素の追加・削除など、末端には意味の無い操作はトレイトに含めない)
- 個別要素(末端)にそのトレイトを実装する
- まとまりを表す型に、そのトレイトを実装した要素の
Vec<Box<dyn Trait>>を持たせ、同じトレイトを実装する - まとまりの処理は、自分の子それぞれへ同じ操作を再帰的に委譲するだけにする。これで、子が末端でも、さらに深いまとまりでも、同じコードで扱える
庭師の一言
一本の枝も、束ねた枝も——同じ鋏。
見習いの手控え
師匠の言葉、要は「個別もまとまりも、同じ手続きで触れるようにする」ってことです。今日は、寄せ植え鉢専用の型を作らずに済んだのがポイントでしたね——Bedが自分自身を子に持てるようにしたことで、入れ子がそのまま表現できるようになりました。
