notes mizzy.org

createの失敗は「外部世界について何が分かっているか」で分ける

確認済み 出典: ChatGPTとの対話(2026-11-06)、carina-rs/carina main 273a2520のcarina-core/src/provider.rs effect.rs plan.rs 作成

createの失敗をResult<T, E>のErr一つで返すと、失敗した操作の中に、既に起きた副作用が含まれている場合を表せない。APIがタイムアウトしても、クラウド側ではリソースができているかもしれない。そこでCarinaは、create失敗を「APIが何を返したか」ではなく、その時点で外部世界について何が分かっているかで分類している。

CreateFailureKindは4つに分かれる。

変種分かっていることstateの扱い
NoResource何も作られていない。リクエストが出ていないか、クラウドが何も作らずに拒否した記録しない
OutcomeUnknownリクエストはクラウドに届いたかもしれないが、識別子も継続も無いアドレスと開始時刻だけを保護された行として残す
IdentifierReported識別子は返ったが、リソースの存在は観測できていないかもしれない識別子を残す
InFlight再開できる継続(トークン)がある。識別子は有ったり無かったり次のapplyで同じ操作に戻れるよう残す

成功の側も二値ではなく、作成後の読み戻しが不完全だったPartialSuccessがある。

分類が回復手段を決める

この分け方が効くのは、変種ごとに次に取るべき手当てが違うからである。とくにOutcomeUnknownは、stateに「あるかもしれない」行として残り、解消されるまでplanをエラーで止める。解消の経路は二つしかない。

どちらも「分からない」を黙って「無い」とも「有る」とも扱わない。黙って捨てるのは、落とすことより悪い

分類を型の進み方で強制する

分類は列挙子を選ぶだけでは守れない。providerが勝手にSuccessを組み立てれば終わりだからだ。CarinaではCreateOutcomeのフィールドがprivateで、成功値を直接作るコードはコンパイルできない(compile_failのdoctestがある)。providerは、executorが渡したCreateAttemptを消費して先へ進めるしかない。

CreateAttempt issued(identifier, sent) IssuedCreate … → 成功 pending(token, …) PendingCreate 後で再開する(Cloneできない) not_created(error) NoResource outcome_unknown(sent, error) OutcomeUnknown sentはhostしか作れない証拠
どの遷移もselfを消費するので、同じ試行から二つの結果は作れない。

どの経路を通るかを決める「送ったかどうか」は、providerではなくhostが観測する。「送ったかどうか」はproviderの申告ではなくhostが観測する。型で壊れた状態を書けなくする原則そのものはtypestateで壊れた状態を書けなくするにある。

根拠の確度

4変種の定義とコメント、CreateOutcomeKindのSuccess / PartialSuccess、CreateAttemptのメソッド、compile_failのdoctest、OutcomeUnknownImportReplacementの生成関数、CreateOutcomeUnknownAbsentResolution::StateForgetはcarina main 273a2520のソースで確認した。表の「stateの扱い」は型とコメントから読んだもので、executorのstate書き込み経路をすべて追ったわけではない(要確認: IdentifierReportedが次のplanでどう扱われるか)。

ChatGPTが会話の中でissuedを(identifier, sent)と引用していた点はCreateAttempt::issuedと一致する。ただしPollAttempt::issuedはidentifierだけを取るので、同名でも型ごとに引数が違う。

#carina #typestate #state