世界を動かす技術を、日本語で。

Rustプロジェクトの目標:不動型と保証されたデストラクタ

2026年8月3日原文(github.com)

概要

Rustの型システムに MoveForget などの新しいトレイトを導入し、型ごとに移動や忘却の可否を明示化 自己参照型確実なデストラクタ実行 など、現状のPinでは困難なパターンに対応 Linuxカーネルなど実例で検証しつつ、 安全なscoped spawnasync drop の実現を目指す Pinの複雑さを解消し、 型プロパティ として不変性や忘却不可を管理 既存のFutureトレイトの変更は今回のスコープ外

Rust型システムの進化:Immobile typesとGuaranteed destructors

  • Rustは 全ての型が移動可能かつmem::forgetで忘却可能 という前提
  • 一部の型(自己参照型や必ずデストラクタを走らせたい型)はこの前提から外れたい要件
  • Pin は「場所」の不変性を保証するが、 として不変性を持たせる方がシンプル
  • Moveトレイト で「この型は移動可能」を明示、!Moveで「移動不可型」を表現
  • Forgetトレイト で「この型はmem::forget可能」を明示、!Forgetで「忘却不可型」を表現
  • 型が!Moveの場合、アドレスが生存期間中ずっと安定、Pinより単純
  • !Forget型は必ずデストラクタが実行されるため、安全なscoped spawnやasync dropが可能
  • Sized階層の拡張と同じ思想で、型の能力をトレイトで管理

モチベーションと課題

  • 自己参照型 (例:多くのasync future)は移動不可が本質的要件
    • Pinによる実装は複雑で、Linuxカーネルのような用途では安全性に限界
  • 確実なデストラクタ実行 が必要な型(例:Transactionやscoped task handle)はmem::forgetで破壊されると安全性が損なわれる
  • 現状のRustではscoped spawnなどのパターンが安全に実装できない

新トレイト導入の具体案

  • Moveトレイト :移動可能性を型のプロパティに
    • #[lang = "move"] unsafe auto trait Move {}
    • !Move型は生涯同じアドレスを保持
    • in-place初期化(#t-lang/in-place-init)が前提
  • Forgetトレイト :忘却可能性を型のプロパティに
    • unsafe impl !Forget for ScopedTaskHandle {}
    • !Forget型は必ずデストラクタ実行
    • 安全なscoped spawnパターンが可能(ハンドルdrop時に必ずjoin)

今後1年の主な作業

  • Moveトレイトのコンパイラ実装・RFC執筆・Linuxカーネル実地検証
  • Iteratorと!Move型の相互作用検証(自己参照を含むgenerator効果のサポート)
  • Guaranteed destructors設計検討(既存機能とのトレイト階層の調整)

スコープ外事項

  • Futureトレイト の変更や移行は今回は対象外
    • Pin依存以外にも多くの課題があるため、将来の独立プロジェクト扱い

よくある質問(FAQ)

  • Sized階層との関係 型の能力(サイズ・移動・忘却)をトレイト階層で明示的に管理する流れの一部

  • Pinエルゴノミクスとの違い Pinの複雑さ(Trait/PinnedTraitの重複・Dropの特例など)を根本解決するため、Pin自体を将来的に非推奨にする方針

  • 安全なscoped spawnの実現要件 !Forget型により、ハンドルのデストラクタが必ず走る=join保証ができる

  • 参考ブログ・設計案

    • Move, Destruct, Leak:デストラクタ・忘却トレイト階層の考察
    • Must move types:呼び出し側に特定のアクションを強制する型設計
    • Ergonomic Self-Referential Types for Rust:Moveトレイトの設計詳細
    • Why Pin is a part of trait signatures:Pinの課題と本提案の動機
    • Placing functions:!Move型のin-place構築構文案

今後の展望

  • Pinの複雑さから脱却し、 型の能力を明示的にトレイトで管理 するRustへ進化
  • 自己参照型・スコープ付き非同期タスク など、これまで難しかった安全パターンの普及促進
  • 既存コードとの互換性維持を重視しつつ、段階的な導入と検証を推進

Hackerたちの意見

Rustにもっと代数的効果が追加されるみたいだね。

どういうこと?これはconstnessやasync、can-panic、can-unwindみたいな「代数的効果」に似た機能とは違う気がする。これは関数の性質じゃなくて、型そのものの特性だから。

うーん、もしかしたらそれは悪いことじゃないかも?

mem::forgetだけが値を安全に漏らす方法じゃないよね。参照サイクルを使ってもできるし、コンパイラがそれを検出する方法はないの?だからmem::forgetが安全なのは、自分で安全に実装できるからじゃない?それをどうやって回避するの?

Sizedと同じようにするんだよ。ジェネリックパラメータにForgetのバウンドを自動的に含めて、忘れなくてもいいメソッドはオプトアウトできるようにする。これで既存のコードはそのままコンパイルできるし、既存のunsafeコードも無効にならない。

mem::forgetだけが値を安全に漏らす方法じゃないよね。参照サイクルを使ってもできるし、コンパイラがそれを検出する方法はないの?でもそれに対する簡単な解決策があるよ。参照カウント付きスマートポインタに、ポインティー型がForgetであることを要求させるんだ。ArcがSendを実装しないのと同じようにね。

これが私の最初の質問でもあったんだけど、元の「spawn」会話からの循環アークの例について触れてるものが見当たらないね。自動的に!Forgetを伝播させる必要があるみたいだけど、「忘れる論理的実装に使えるもの」を作らなきゃいけないんだろうね。特にRcみたいなものがForgetバウンドを取って、エディション境界でそれをやるのが重要そうだけど…リンクにはそのことが全然書かれてないね。

いいニュースだね!2016年頃から、移動できない型がRustにとって重要な欠けている部分だって分かってたけど、長い間それを追加するのは無理だと思われてた。だからPinハックが生まれたんだ。最終的にそれを追加する方法が見つかって本当に良かった。言語の大きな穴を埋めてくれてるからね。

最終的にそれを追加する方法を見つけてくれて本当に嬉しい。 既存のコードとPinが統合されるのかな? そうじゃないと、エコシステムがさらに分裂しちゃうよね…。

目標の一部ではないけど、!Destruct/"必ず移動する型"、つまり線形型についても触れてるね:常に引数なしで値をドロップできるわけじゃなくて、線形型の値を捨てたいなら、その値を取る関数を呼ばなきゃいけない。

文脈として、これが本当に素晴らしいのは、特定のエラーをキャッチするAPIデザインを可能にするからなんだ。let txn = create_transaction(); // トランザクションで何かをする txn.commit(); // txnを消費する 現在、ユーザーがcommit()を呼ばない限り静かにロールバックするか、ユーザーがcommit()rollback()を明示的に呼ばなかった場合にトランザクションのDrop実装でパニックを起こすかのどちらかを選ばないと、このAPIを実装できない。今のところの選択肢はクロージャを使うことだけど、それはあまりコンポーザブルじゃない。各フレーバーに対してバリアントが必要だから:失敗しないもの、失敗するもの、非同期失敗するもの、など。 start_transaction_async(async || { /* ... / TransactionResult::Commit }); start_transaction_async_try(async || { / ... */ Ok(TransactionResult::Commit }); うーん。もしトランザクションが必ず移動する型なら、commitかrollbackのどちらかを正確に呼ばないとコンパイラエラーが出るし、特にすべての出口ポイントで何が起こるかを考慮しなきゃいけなくなる(?で早期リターンしてもトランザクションを忘れない)。すごくいいね。

リニアタイプをコアの組み込みコレクションや型に組み込むにはかなりの作業が必要だよ。Mojoで組み込み型やコレクションのためのリニアタイプサポートの作業を追ってるけど、Rustの言語セマンティクスじゃ同じレベルの統合は無理だと思う(Rustはもう安定してるし)。

Hacker Newsで議論の続きを見る