Rust 1.100を1.99と比べて検証|never型の変更で止まるコード、公開直後の依存を外すCargoの設定、未使用依存の警告

Rust 1.100で、never型が安定化し、Cargoに公開直後の版を外す設定が入った

Rustのリリースチームが、2026年11月12日にRust 1.100.0を公開しました(Rust公式ブログの告知)。告知の最初に挙がっているのは、「決して値を返さない」ことを表す型!(never型)の安定化です。公式ブログは、10年にわたり5回失敗してきた安定化がようやく実現したと説明しています。

never型の安定化に合わせて、returnや?のように値を返さない式が関わり、ほかに型を決める手がかりがないときに当てはめる型(never型のフォールバック)が、2024より前のエディションでも()から!に変わりました。リリースノートでは互換性の注意に入っている変更で、これまでコンパイルできたコードが止まることがあります。同じ版で、独自のメモリーアロケーターをVecやBoxに渡せるAllocatorも安定化し、Cargoには、公開から日の浅い依存の版を選ばない設定(min-publish-age)と、使っていない依存の警告が加わりました(1.100.0のリリースノート)。

この記事では、Docker Hubの公式イメージrust:1.99.1-slimとrust:1.100.0-slimで同じコードを動かし、1.100で止まるコード、新しく書けるようになったコード、Cargoの新しい設定と警告を比べました。1.100へ上げる前に何を確かめればよいかを、最後にまとめます。

要点

1.100へ上げると、never型のフォールバックに頼っていた呼び出しが止まり、Cargoで公開直後の依存を外せる

Rust 1.99.1と1.100.0の公式イメージで、同じコードと設定を比べた結果です。

止まるコード

型引数が戻り値にしかない関数を?演算子で呼んで値を捨てると、E0277になることがある

1.99.1でlintを抑えて通していたコードも、1.100.0では止まりました。

新しく書けるコード

Result<T, !>と、Vec::new_inの独自アロケーター

どちらも1.99.1では「実験的な機能」のエラーでした。

Cargoのmin-publish-age

"7 days"で、5日前に出た版を選ばなかった

設定は.cargo/config.tomlの[registry]に書きます。1.99.1は無視しました。

未使用の依存

cargo checkが警告を出す

[lints.cargo]でエラーにも無効にもできます。

型引数が戻り値にしかない関数を?演算子で呼んで値を捨てると、1.100ではE0277で止まることがある

never型のフォールバックは、returnや?、panic!のように値を返さない式が関わっていて、ほかに型を決める手がかりがないときに、コンパイラーが当てはめる型です。たとえば、型引数が戻り値にしか出てこないジェネリック関数を?付きで呼び、戻り値を使わずに捨てると、?の中の早期リターンが関わる一方で、戻り値の型には手がかりがありません。1.99までの2015・2018・2021エディションでは、この型を()にしていました。1.100では、すべてのエディションで!になります(2024エディションは以前から!でした)。

次のコードで比べました。makeはDefaultを実装した型なら何でも返せる関数で、runでは型を書かずに呼んでいます。

fn make<T: Default>() -> Result<T, String> {
    Ok(T::default())
}

fn run() -> Result<(), String> {
    make()?;
    Ok(())
}

2021エディションでは、1.99.1でもコンパイルは止まります。ただし理由は、既定でエラーになるlint(dependency_on_unit_never_type_fallback)です。1.100.0では、型が!に決まり、!はDefaultを実装していないため、型のエラーE0277になりました。

止まるのは、当てはめた!が型引数の条件(この例ではDefault)を満たさないときです。引数から型が決まるwrap(5u8)?;やstd::fs::write(...)?;、!も満たすT: Debugの関数を同じように呼ぶ書き方は、1.99.1と1.100.0のどちらでもコンパイルできました。

Rust 1.99.1(2021エディション)

lintのエラー。抑えればコンパイルできた

error: this function depends on never type fallback being `()`。#![allow(...)]で抑えると、Ok(())と表示して動きました。

Rust 1.100.0(2021エディション)

型のエラー。抑えられない

error[E0277]: the trait bound `!: Default` is not satisfied。#![allow(...)]は「lintが削除された」という警告になり、E0277は残りました。

1.100.0のエラーには、型推論の変更が原因かもしれないという注記と、()を意図していた可能性の案内が付きます。

error[E0277]: the trait bound `!: Default` is not satisfied
 --> src/main.rs:6:5
  |
6 |     make()?;
  |     ^^^^^^ the trait `Default` is not implemented for `!`
  |
  = note: this error might have been caused by changes to Rust's type-inference algorithm (see issue #148922 <https://github.com/rust-lang/rust/issues/148922> for more information)
  = help: you might have intended to use the type `()` here instead

直し方は、1.99.1のlintが示していたとおり、型を書くことです。make::<()>()?;にすると、1.99.1と1.100.0の両方でOk(())と表示しました。

注意したいのは、自分のコードより依存クレートです。Cargoは、crates.ioやgitから取った依存のlintを抑えて(--cap-lintsで上限をかけて)ビルドします。同じ書き方の関数を持つクレートをgitの依存として使うアプリを作ると、1.99.1ではビルドが通ってOk(())と表示し、1.100.0では依存のクレートのコンパイルがE0277で止まりました。1.99.1でも、-qを付けずに実行すると、将来のRustで拒否されるコードを含むパッケージとして依存の名前を挙げる警告が出て、詳しい内容をcargo report future-incompatibilitiesで見る方法が添えられていました。公式ブログは、影響を受ける公開クレートには修正版が出ていて、依存のビルドが止まった場合は依存を更新すると直ることがあると案内しています。

!型を書けるようになり、Infallibleは!の別名になった

never型の安定化で、!を関数の戻り値以外の場所にも書けるようになりました。失敗しない処理をResult<u32, !>と書けば、let Ok(n) = ...だけで値を取り出せます。1.99.1ではerror[E0658]: the `!` type is experimentalで止まりました。

use std::convert::Infallible;

fn parse(s: &str) -> Result<u32, !> {
    Ok(s.len() as u32)
}

fn main() {
    let Ok(n) = parse("abc");
    println!("n = {n}");
    println!("Infallible = {}", std::any::type_name::<Infallible>());
}

// 1.100.0の出力
// n = 3
// Infallible = !

これまで「失敗しない」ことを表していたstd::convert::Infallibleは、1.100では!の型の別名になりました。上の出力のとおり、型名も!です。impl From<Infallible> for MyErrorを書いた型に、impl From<!> for MyErrorを足すと、1.100.0では同じ実装が2つあるというエラー(E0119)になりました。Infallibleだけに実装した1.99までのコードは、1.100.0でも変更なしでコンパイルでき、?での変換も動きました。

Vecに独自のアロケーターを渡せるようになった

1.100では、メモリーの確保と解放を受け持つAllocatorトレイトと、それを受け取るVec::new_inやBox::new_inなどが安定化しました。特定のVecだけ、確保した量を数えるアロケーターや、まとめて解放できる領域から確保するアロケーターを使えます。

確保したバイト数を数えて標準のGlobalに渡すだけのアロケーターを作り、Vecにu64を100個入れました。

use std::alloc::{AllocError, Allocator, Global, Layout};
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};

struct Counting {
    allocated: AtomicUsize,
}

unsafe impl Allocator for &Counting {
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
        self.allocated.fetch_add(layout.size(), Ordering::Relaxed);
        Global.allocate(layout)
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        unsafe { Global.deallocate(ptr, layout) }
    }
}

fn main() {
    let counter = Counting { allocated: AtomicUsize::new(0) };
    let mut v = Vec::new_in(&counter);
    for i in 0..100u64 {
        v.push(i);
    }
    println!("len = {}, allocated bytes = {}", v.len(), counter.allocated.load(Ordering::Relaxed));
}

// 1.100.0の出力
// len = 100, allocated bytes = 2016

2016バイトは、容量が4、8、16、32、64、128要素へ増えるたびに確保した量(32+64+128+256+512+1024バイト)の合計です。1.99.1ではerror[E0658]: use of unstable library feature `allocator_api`で止まり、安定版では書けませんでした。公式ブログは、今回の安定化を、ほかのコレクションやポインター型へ広げていく土台と位置づけています。

Cargoは、公開から日の浅い版を選ばないように設定できる

1.100のCargoには、公開から一定の期間がたっていない依存の版を、版を選ぶときに外す設定が加わりました。公開直後に乗っ取られたり不正な版が出されたりしても、その版をすぐには取り込まないための設定です。プロジェクトの.cargo/config.tomlに書きます。

[registry]
global-min-publish-age = "7 days"

依存にsyn = "3"を書いたプロジェクトで試しました。crates.ioでは、syn 3.0.10が2026年11月9日(UTC)、3.0.9が10月12日に公開されています。検証した時点で、3.0.10は公開から約5日でした。

$ cargo generate-lockfile
    Updating crates.io index
     Locking 4 packages to highest Rust 1.100.0 compatible versions as of 7 days ago
      Adding syn v3.0.9 (available: v3.0.10, published 5 days ago)

設定がなければ3.0.10、"7 days"なら3.0.9、"3 days"なら3.0.10が選ばれました。環境変数CARGO_REGISTRY_GLOBAL_MIN_PUBLISH_AGE='7 days'でも同じ結果です。値には、数字のあとにseconds、minutes、hours、days、weeks、monthsのどれかを書きます(Cargoの設定のドキュメント)。既定は"0"で、何も外しません。

すでにCargo.lockがあるプロジェクトに、あとから設定を入れた場合の動きも確かめました。

min-publish-ageを"7 days"にしたときのCargo 1.100.0の動き

ロックファイルに3.0.10があるとき

cargo checkはロックファイルの3.0.10のままビルドしました。ロックファイルにある版は外しません。

cargo update -p syn

Downgrading syn v3.0.10 -> v3.0.9と、版を下げました。設定を入れたあとの更新では、ロックファイルにある日の浅い版が、条件を満たす版まで下がります。

cargo update -p syn --precise 3.0.10

version 3.0.10 is too new (published 5 days ago, minimum age 7 days)のエラーで止まりました。

環境変数のallowで3.0.10を入れる

エラーの案内どおりCARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allowを付けると、3.0.10に上がりました。ドキュメントによると、この方法では、指定した版が必要とする依存も、公開からの日数を確かめずに取り込まれます。

allowを付けずに、次のcargo update

設定を残したままcargo updateを実行すると、Downgrading syn v3.0.10 -> v3.0.9と元の版に戻りました。cargo checkだけなら、ロックファイルの3.0.10のままです。

急ぎのセキュリティ修正をallowで入れた場合、その版の公開から設定した日数がたつまでは、cargo updateを実行するたびに元の版へ戻ります。この動きを変えるかどうかは、Cargoのリポジトリで議論が続いています(cargo#17642)。その間は、更新のあとのロックファイルの差分で、入れた版が戻っていないかを確かめます。

同じ設定ファイルを1.99.1のCargoで読むと、warning: ignoring `registry.global-min-publish-age` without `-Zmin-publish-age`と警告を出して設定を無視し、3.0.10を選びました。チームやCIで1.99以前のCargoが混ざっていると、同じリポジトリでも選ばれる版が変わります。

公式ブログは、この設定だけでは守りとして十分にならないと書いています。レジストリの索引に公開日時がない版は外れず、cargo installはこの設定を使いません。

使っていない依存を、Cargoが警告するようになった

1.100のCargoには、Cargo自身のlintの仕組みが入り、その1つとして使っていない依存を知らせるcargo::unused_dependenciesが既定で有効になりました。上のsynはコードで使っていないので、1.100.0のcargo checkは次の警告を出しました。1.99.1では何も出ません。

warning: unused dependency `syn`
 --> Cargo.toml:7:1
  |
7 | syn = "3"
  | ^^^^^^^^^
  |
  = note: `cargo::unused_dependencies` is set to `warn` by default
help: consider removing the dependency on `syn`

警告の強さは、Cargo.tomlの[lints.cargo]で変えられます。CIで止めたいならunused_dependencies = "deny"、出したくないなら"allow"にします(Cargoのlintのドキュメント)。ドキュメントによると、[dev-dependencies]は調べず、cargo check --bin fooのようにビルドする対象を絞ると[dependencies]も調べません。機能を有効にするためだけに書いた依存は、使っていない依存と判定されることがあります。

1.100へ上げる前に確かめる5つのこと

上げる前の確認

1. 1.99のうちに、将来の非互換の警告を確かめたか

1.99で-qを付けずにビルドし、将来のRustで拒否されるコードを含む依存の警告が出ていれば、cargo report future-incompatibilitiesで中身を見て、その依存を新しい版に上げておきます。

2. 1.100で全体をビルドし、新しいエラーが出ないか

E0277に「型推論の変更が原因かもしれない」という注記が付いていれば、never型のフォールバックの変更です。自分のコードは型を書き、依存のクレートなら新しい版に上げます。リリースノートの互換性の注意には、if letで使う変数をクロージャーが取り込む範囲の変更など、ほかにも新しいエラーになりうる変更が並んでいます。

3. dependency_on_unit_never_type_fallbackを抑えていないか

#[allow(...)]で抑えていた箇所は、1.100でE0277になります。リポジトリをこの名前で検索します。

4. min-publish-ageを何日にするか

入れる場合は、全員とCIのCargoを1.100以上にそろえます。設定後のcargo updateで版が下がり、allowで入れた版も次の更新で戻るので、ロックファイルの差分を確かめます。

5. 中間生成物のフォルダー(build-dir)を直接読むツールがないか

リリースノートによると、Cargoが中間生成物を置くフォルダーの配置が変わりました。生成物の場所は、CargoのJSONメッセージから取る形にします。

never型の変更で止まるのは、値を返さない式が関わり、型の手がかりがないまま()に頼っていたコードで、多くは型を1つ書けば直ります。Cargoのmin-publish-ageは、ロックファイルを作るときと更新するときに効く設定なので、まずCIのcargo updateの手順に入れ、選ばれた版と、外された版の表示を見ながら日数を決めると、影響を確かめながら導入できます。

参考資料

検証環境

  • macOS 27.0.1(Apple Silicon)、Docker 29.5.3(Docker Desktop)
  • Docker Hubの公式イメージ rust:1.99.1-slim(rustc 1.99.1)と rust:1.100.0-slim(rustc 1.100.0)、arm64
  • 検証日: 2026年11月14日

この記事は、AIが調査と下書きを担当し、上記の環境で実際に動かして結果を確かめ、下書きとは別の担当が公開前に本文と出典を確認しています。制作と訂正の方針は編集・訂正方針で公開しています。