> ## Content Index
> Fetch the complete content index at: https://www.sakashita.page/llms.txt
> Use this file to discover other available public pages before exploring further.

# Rust 1.99の変更をコードで追う
- URL: https://www.sakashita.page/rust-1-99-release-notes-and-analysis/
- Published: 2026-10-25T05:00:00.000Z
- Updated: 2026-10-25T04:59:59.000Z
- Description: C variadic functionの定義、所有権を移すNonNull API、raw pointerからのlayout取得と、1.99.1のABI修正を解説します。
- Author: Sakashita Yasunobu

Rust 1.99.0は2026年10月1日、修正版の1.99.1は10月15日に公開されました。 1.99.0ではC variadic functionの定義がstableになり、`Vec`や`Box`の所有権を`NonNull`で受け渡すAPIも加わっています。 1.99.1では、Cとの境界に関わる`bool`のABIなど、3件のregressionが修正されています（[1.99.0の公式発表](https://blog.rust-lang.org/2026/10/01/Rust-1.99.0/)、[1.99.1の公式発表](https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/)）。

## Cの可変長引数をRust側で受け取る

従来のstable Rustでも、C variadic functionを宣言して呼び出すことはできました。 1.99では`extern "C"`と`extern "C-unwind"`のfunctionをRust側で定義できるため、Cから呼ばれる可変長引数の処理もRustで実装できます。 引数の`...`に対応する`VaList`から、`next_arg::<T>()`で値を順に取り出します（[VaListのAPI](https://doc.rust-lang.org/std/ffi/struct.VaList.html)）。

```rust
unsafe extern "C" fn sum(count: u32, mut values: ...) -> i32 {
    let mut result = 0;
    for _ in 0..count {
        result += unsafe { values.next_arg::<i32>() };
    }
    result
}

fn main() {
    println!("sum={}", unsafe { sum(3, 4_i32, 5_i32, 6_i32) }); // => sum=15
}

```

コード例の対象はRust 1.99.1、darwin/aarch64です。 `count`は残りの引数の個数を示す独自の約束であり、`VaList`が残数や型を検査しているわけではありません。 呼び出し側が2個しか渡していないのに3個読み出したり、実際には整数の引数を`f64`として取り出したりすると、`next_arg`の安全性の条件に違反します。

Cのdefault argument promotionにも従う必要があります。 たとえば`float`は`double`に、小さい整数型は整数昇格後の型になります。 したがって、C側で`float`だった値をRust側で`next_arg::<f32>()`として受け取る実装にはできません。 引数を渡した後のABI上の型に合わせて読み出します（[VaList::next\_argのSafety](https://doc.rust-lang.org/std/ffi/struct.VaList.html#method.next%5Farg)）。

`VaList`の初期化と終了処理はcompilerが用意し、複製には`Clone`を使えます。 複製した値は`va_copy`に対応する独立した走査位置を持ち、破棄時には`va_end`に対応する処理が行われます。 元の走査位置を保持したまま引数を2回走査する場合も、内部表現をコピーせず`clone()`を使います（[VaListの複製と破棄](https://doc.rust-lang.org/std/ffi/struct.VaList.html#impl-Clone-for-VaList%3C'f%3E)）。

### 呼び出しの個数と型を、functionの型だけでは表せない

固定の引数だけを持つfunctionなら、引数の個数や型が宣言に現れます。 可変部分を`...`で受け取るfunctionでは、可変部分の型を宣言に列挙できません。 `sum`の第1引数が3であることと、後ろに3個の整数があることは、呼び出し側と実装側で共有する約束です。 compilerが3という値から後続の引数を検査する仕組みではありません。 したがって、Rustで定義できるようになった後も、Cの呼び出し規約に依存する境界は残ります（[C variadic functionの規則](https://doc.rust-lang.org/reference/items/functions.html#variadic-functions)）。

同じ理由で、`next_arg`のtype argumentは、実装が次に読み出す値の解釈を決めます。 実引数を調べて適切な型を選んでくれる操作ではありません。 たとえば整数、浮動小数点数、pointerを受け取る独自の形式なら、実装も指定された順序を維持します。 順番を間違えた後で残りの引数の解釈を正しく戻せる保証はなく、不一致を通常の`Result`として回収するAPIでもありません。 可変長引数を受けるfunction自身で、読み出す型の集合と順番を決めます（[VaList::next\_arg](https://doc.rust-lang.org/std/ffi/struct.VaList.html#method.next%5Farg)）。

`sum`を呼ぶ実引数と、`next_arg`で取り出す型には、同じ`i32`を指定しています。 単に`4`とだけ書いた値のtype inferenceへ依存せず、呼び出し規約で受け渡す型をコードに残すためです。 加算結果の15は、4、5、6を順に取得できた場合の結果です。 別の型や個数を受け取る呼び出しには、受け取る形式に対応した読み出し処理が必要です。 functionをCへ公開する場合も、利用者が渡す形式を別に定める必要があります。

`extern "C-unwind"`を選ぶ条件と、可変長引数を扱う条件は別です。 ABIを越えるunwindを許す境界と、許さない境界を区別する指定です。 `VaList`の型検査を追加する指定ではありません。 可変部分の読み出しに失敗する呼び出しを、unwind可能なABIへ変えるだけで正しく扱えるようにはなりません。 可変長引数の契約と、例外やpanicが境界を越える契約を、呼び出し規約に合わせます（[RustonomiconのFFIとunwind](https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding)）。

### `VaList`を複製した位置から読み直す

走査位置の複製は、最初から引数を取り直す操作とは異なります。 最初の整数4を取得してから`VaList`を複製すると、元の値と複製した値の両方が、次の整数5の手前を指します。 元の位置は次の整数5へ進んでいるので、複製した位置も5の手前になります。

```rust
unsafe extern "C" fn cursors(_count: u32, mut values: ...) -> i32 {
    let first = unsafe { values.next_arg::<i32>() };
    let mut copied = values.clone();
    let second = unsafe { values.next_arg::<i32>() };
    let second_copy = unsafe { copied.next_arg::<i32>() };
    println!("cursors={first}/{second}/{second_copy}"); // => cursors=4/5/5
    first + second + second_copy
}

fn main() {
    let total = unsafe { cursors(3, 4_i32, 5_i32, 6_i32) };
    assert_eq!(total, 14);
}

```

5を2回読んでいるのは、同じ走査位置を2つの独立した状態から読んだからです。 元の`values`から5を読んでも、複製した`copied`は5の手前に残ります。 可変長引数を数える処理と実際に処理する操作を分ける場合にも、必要なのは走査位置の複製であり、引数のbyte列を手作業で複製することではありません。

`cursors`のbodyは`_count`を参照しません。 `cursors`のbodyは先頭2個の整数だけを読み、残数を計算しません。 `sum`のように個数に従ってすべて読むAPIへ変える場合は、複製した位置から残り何個を読むかも実装で管理します。 複製した後に最初の個数を繰り返し回数に使うと、すでに消費した分を余計に読み出す場合があります。 走査位置の保存と、残数の管理は別の状態です。

新しい定義機能は、Rustで型の決まったsliceを受け取るfunctionを、可変長引数へ自動変換するものでもありません。 sliceには要素の型と長さが付随しますが、C variadic functionでは可変部分の型を共有する別の形式が必要です。 Rust同士の呼び出しだけで完結するAPIなら、sliceで表現できる情報を可変部分へ移す必然性はありません。 1.99の追加が直接埋めるのは、C側が要求する可変長引数のentry pointをRust側で定義する部分です（[RustのfunctionとABI](https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier)）。

## `NonNull`で所有権を外へ移し、同じallocationを取り戻す

`Vec::into_parts`は`Vec`を消費し、`NonNull<T>`、長さ、capacityを返します。 返された値を`Vec::from_parts`に渡せば、同じallocationを所有する`Vec`を再構築できます。 要素を再コピーせず、allocationの管理を移します（[Vec::into\_parts](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.into%5Fparts)）。 以下の断片は`main`のbodyへ置きます。

```rust
let values = vec![String::from("red"), String::from("blue")];
let (pointer, length, capacity) = values.into_parts();
let restored = unsafe { Vec::from_parts(pointer, length, capacity) };
println!("owned={restored:?}"); // => owned=["red", "blue"]

```

`into_parts`の後は元の`Vec`が存在しないため、`restored`の破棄で要素とallocationが解放されます。 `from_parts`を同じ値で2回呼び出すと、allocationを解放する所有者が重複します。 また、capacityや要素型を変えると、allocationと解放時のlayoutが一致しなくなり得ます。 `NonNull`が保証するのはnullではないことまでであり、所有権、初期化済みの要素数、allocationのlayoutは呼び出し側が維持します（[Vec::from\_partsのSafety](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.from%5Fparts)）。

![Vecから取り出したpointer、length、capacityを再構築したVecへ移す図](https://storage.ghost.io/c/94/1f/941f9972-ad5c-4231-a438-ca329c85433f/content/images/2026/10/rust-ownership.png "into_partsで所有権が外へ移り、from_partsでVecの管理に戻ります。")

Vecから取り出したpointer、length、capacityを再構築したVecへ移す図

`Box`にも`into_non_null`と`from_non_null`が加わりました。 `Box::into_non_null(boxed)`は`Box`を消費して`NonNull<T>`を返し、`Box::from_non_null(pointer)`は所有者を再構築します。 後で解放する値の所有権を渡す場合、`Box::leak`を経由せず往復できます（[into\_non\_null](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.into%5Fnon%5Fnull)、[from\_non\_null](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.from%5Fnon%5Fnull)）。

```rust
let boxed = Box::new(String::from("owned"));
let pointer = Box::into_non_null(boxed);
let restored = unsafe { Box::from_non_null(pointer) };
println!("box={restored}"); // => box=owned

```

`Box`のallocationを保持したまま所有者だけを外し、最後に`Box`へ戻しているため、値を意図的に残し続ける処理は含まれていません。

### `NonNull`へ変えても、所有者の個数は別に管理する

`Vec::into_parts`の返り値にあるpointerは、要素の先頭を示します。 長さは初期化済みの要素数、capacityはallocationが確保している要素数です。 再構築する側では、破棄する要素数と、allocationの解放に使うlayoutを両方復元します。 長さだけを保存してcapacityを失うと、要素の境界は分かっても、元のallocationを正しい条件で解放できなくなります（[Vecの保証](https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees)）。

`Vec<String>`の場合は、allocationの区別がさらに必要です。 `Vec`は`String`を並べる領域を確保し、各`String`は文字列用のallocationを別に持ちます。 `into_parts`で外へ移すのは、並んだ`String`の所有権も含む`Vec`全体の管理です。 再構築した`Vec`を破棄すると、初期化済みの`String`のdestructorが動き、各文字列のallocationも解放されます。 要素用の領域だけを解放して済む操作にはできません（[Rustonomiconのdestructor処理](https://doc.rust-lang.org/nomicon/vec/vec-dealloc.html)）。

破棄時にcounterを増やす要素を使うと、所有権の移動と要素の破棄を分けて表せます。

```rust
use std::cell::RefCell;
use std::rc::Rc;

struct Tracked(Rc<RefCell<usize>>);

impl Drop for Tracked {
    fn drop(&mut self) {
        *self.0.borrow_mut() += 1;
    }
}

let drops = Rc::new(RefCell::new(0));
let values = vec![Tracked(drops.clone()), Tracked(drops.clone())];
let address = values.as_ptr();
let (pointer, length, capacity) = values.into_parts();
assert_eq!(*drops.borrow(), 0);
let restored = unsafe { Vec::from_parts(pointer, length, capacity) };
assert_eq!(address, restored.as_ptr());
drop(restored);
assert_eq!(*drops.borrow(), 2);

```

`into_parts`は要素を破棄せず、`from_parts`は元のallocationを再利用します。 counterが増えるのは、所有権を受け取った`restored`を破棄する段階です。

`NonNull`のpointer値を複製しても、要素とallocationの所有権は複製されません。 複製したpointerを使って2つの`Vec`を再構築してしまうと、要素とallocationを破棄する権限まで重複させます。 最初の`Vec`を破棄すると、残った`Vec`はすでに解放した領域を保持します。 新APIがnullの扱いを型へ移しても、再構築を1回に限る契約は`from_parts`を呼ぶ側に残ります（[NonNullの契約](https://doc.rust-lang.org/std/ptr/struct.NonNull.html)）。

`Box::into_non_null`でも、pointerの値を保存することと、解放を担当する所有者を保存することを区別します。 `Box`を消費した直後には、通常のscope終了で値を解放する`Box`は残りません。 再構築しない経路を選べば、値を解放する処理も自動では復活しません。 たとえば外部のcallbackへpointerを渡す設計では、成功時だけでなく、callbackを登録できなかった経路でも所有権を回収する必要があります。 登録先に所有権を渡せた経路と、呼び出し元へ戻す経路を同時に成立させない設計にします（[Box::from\_non\_nullのSafety](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.from%5Fnon%5Fnull)）。

従来の`ManuallyDrop`を使った実装では、自動のdestructorを止めてからpointer、長さ、capacityを取り出す方法がありました。 `ManuallyDrop`は内側の値のlayoutやbitの有効性を変えず、破棄を抑制する型です。 したがって、破棄済みの値を後から安全な参照として公開してよい状態へ変える型ではありません。 `into_parts`は`Vec`を消費する呼び出しに管理の移動をまとめるため、破棄を止めた`Vec`を別に持ち続ける必要を減らします（[ManuallyDropのAPI](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html)）。

`Box::leak`から参照を得る使い方も、所有権を移す使い方とは状態が異なります。 `leak`は`Box`が持っていた値を自動解放しない状態へ移し、参照を返します。 後から参照をraw pointerへ変換して所有者を復元する設計では、まだ利用できる参照を残していないかまで考える必要があります。 最初から外部へ所有権を移して戻す用途なら、参照を作る過程を挟まず、消費するAPIで管理の移動を示せます（[Box::leak](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak)）。

### 空の`Vec`とzero-sized typeはallocationの例外になる

空の`Vec<u32>`では、長さとcapacityは両方0です。 ただし、返されるpointerの型は`NonNull<u32>`です。 要素を1個も確保していない状態でも、null pointerを返すAPIにはなっていません。 nullではないpointerを、実際のallocationや初期化済みの値が存在する証拠として扱うことはできません（[Rustonomiconの空のVec](https://doc.rust-lang.org/nomicon/vec/vec-alloc.html)）。

空のcollectionを外部へ渡す際に、長さ0ならpointerを使わない約束を設けるAPIはあります。 しかし、外部APIの空の状態をnullで表す契約なら、`NonNull`の値を渡すだけで同じ表現になるわけではありません。 Rust側の空collectionの表現と、外部APIが受け取る空の表現を接続する処理が必要です。 反対方向から受け取る場合も、nullを`from_parts`へ無条件で渡す操作にはできません。 受け渡す側の型が保証する状態を、相手側の表現へ変換します。

zero-sized typeでは、要素数と確保byte数の対応も変わります。 `vec![(); 3]`では、長さは3、capacityは`usize::MAX`になります。 `()`を3個保持していても、要素用に3 byte以上のallocationを確保したことにはなりません。 sizeが0の型は、要素数を増やしても要素用のbyte数が増えないためです（[Rustonomiconのzero-sized type](https://doc.rust-lang.org/nomicon/vec/vec-zsts.html)）。

zero-sized typeを扱う実装では、pointerの数値を要素sizeだけ増やして反復位置を表す方法を流用できません。 要素sizeを掛けた移動量は0なので、異なる論理的な要素の間でもbyte addressは変わりません。 一方、要素数の上限やdestructorを呼ぶ回数は依然として管理する必要があります。 genericな所有権移動に新APIを使う場合も、要素数、capacity、allocationのbyte数を別に管理します。 zero-sized typeでは、要素数からallocationのbyte数を逆算できないためです（[zero-sized typeの反復とcapacity](https://doc.rust-lang.org/nomicon/vec/vec-zsts.html)）。

## 参照を作らずに動的なlayoutを取得する

`Layout::for_value_raw`、`mem::size_of_val_raw`、`mem::align_of_val_raw`もstableになりました。 いずれもraw pointerから、指している値のsizeやalignmentを求めます。 sliceなら長さ、trait objectならvtableなど、unsized typeに付随するmetadataを使って動的なlayoutを決めます（[Layout::for\_value\_raw](https://doc.rust-lang.org/stable/core/alloc/struct.Layout.html#method.for%5Fvalue%5Fraw)）。

```rust
use std::alloc::Layout;

let data = [1_u16, 2, 3];
let pointer: *const [u16] = &data[..];
let layout = unsafe { Layout::for_value_raw(pointer) };
println!("layout={}/{}", layout.size(), layout.align()); // => layout=6/2

```

`u16`が3個なのでsizeは6 byteであり、alignmentは2 byteになります。 `&data[..]`は有効なsliceを作りますが、raw版のAPIが任意のmetadataを受け付けるわけではありません。 たとえばsliceの長さが初期化されていることや、全体のsizeが`isize`の範囲に収まることなど、API固有の条件を満たす必要があります（[for\_value\_rawのSafety](https://doc.rust-lang.org/stable/core/alloc/struct.Layout.html#method.for%5Fvalue%5Fraw)）。

### raw pointerからの照会が必要になる位置

`size_of::<T>()`は、型だけでsizeが決まる場合に使えます。 `[u16]`のようなsliceでは長さが型名に含まれないため、具体的な値に付随するmetadataからsizeを求めます。 `size_of_val`は参照を受け取り、`size_of_val_raw`はraw pointerを受け取ります。 新APIは、raw pointerを扱う処理の途中で、照会のためだけに参照を作る必要を減らします（[size\_of\_val\_raw](https://doc.rust-lang.org/std/mem/fn.size%5Fof%5Fval%5Fraw.html)）。

たとえばallocationの管理を外へ移した後でも、unsizedな値のlayoutを保持しておく必要はあります。 再構築や解放で使うsizeとalignmentは、値の中身を読み取る処理とは別の情報です。 raw版のAPIはlayoutの照会を表現しますが、pointerが値を読み出せる状態にあると追加で保証するわけではありません。 照会できたlayoutから、参照を作れることや、参照を使って値を変更できることまで導くことはできません（[align\_of\_val\_raw](https://doc.rust-lang.org/std/mem/fn.align%5Fof%5Fval%5Fraw.html)）。

trait objectでは、長さの代わりにvtableから具体的な型のlayoutを求めます。 `[u8; 12]`を`&dyn Debug`として扱う場合も、raw pointerに付随するvtableからlayoutを求めます。 `dyn Debug`という名前だけから12という値は決まらず、元の具体的な型に対応するmetadataが結果を決めています。

```rust
let value = [7_u8; 12];
let erased: &dyn std::fmt::Debug = &value;
let pointer = erased as *const dyn std::fmt::Debug;
let layout = unsafe { Layout::for_value_raw(pointer) };
println!("erased-layout={}/{}", layout.size(), layout.align()); // => erased-layout=12/1

```

`erased as *const dyn Debug`は、有効なtrait objectの参照をraw pointerへ変換します。 vtableを任意の整数値から組み立てる処理や、別の具体型のmetadataへ付け替える処理は含みません。 layout照会のためのmetadataが正しい経路で得られていることを、呼び出しの前提にしています。 raw pointerを受け付けるというAPIの形だけを根拠に、任意のbit列を入力できるとは解釈できません（[Layout::for\_value\_rawの契約](https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.for%5Fvalue%5Fraw)）。

sliceの例でsizeが6、alignmentが2だったのも、同じ分離を示します。 長さ3は要素数であり、sizeの6はbyte数です。 alignmentの2は、先頭addressに要求する境界です。 要素数、全体のbyte数、addressの条件は互いに別の数量なので、allocation APIへ渡す場合も、正しい役割の値を使います。

`Layout`のsizeが求められても、allocatorから同じsizeだけ確保し直せば、既存の値を移動してよいとは限りません。 値の所有権、初期化状態、pinningの契約はlayoutとは独立して残ります。 1.99で追加された所有権移動のAPIとlayout照会のAPIを組み合わせる場合も、分解時の管理情報と、型やmetadataから得るlayout情報を混同しない構成にします。 前者は解放を担当する所有者の状態を表し、後者は確保領域に必要な形を表します。

## collectionと文字列の小さな追加

`VecDeque::retain_back(len)`は、末尾から`len`個を残して先頭側を削除します。 既存の`truncate(len)`が先頭側を残すのに対し、直近の履歴を残す処理を1回の呼び出しで表現できます。 `len`が現在の長さ以上なら変更しません（[VecDeque::retain\_back](https://doc.rust-lang.org/stable/std/collections/struct.VecDeque.html#method.retain%5Fback)）。

```rust
use std::collections::VecDeque;

let mut queue = VecDeque::from([10, 20, 30, 40]);
queue.retain_back(2);
println!("tail={queue:?}");

```

出力は`tail=[30, 40]`です。 predicateを渡す既存の`retain`とは異なり、`retain_back`に渡す値は残す個数です。

### 末尾の履歴を残す処理と、要素ごとの選別を分ける

`truncate`は先頭側を残し、`retain_back`は末尾側を残します。

```rust
let mut head = VecDeque::from([10, 20, 30, 40]);
let mut tail = head.clone();
head.truncate(2);
tail.retain_back(2);
assert_eq!(head, VecDeque::from([10, 20]));
assert_eq!(tail, VecDeque::from([30, 40]));

```

同じ長さ2への短縮でも、保存したい履歴が古い側か新しい側かで呼び出す操作が変わります。 `VecDeque`を新しい要素を末尾へ追加する履歴として使う場合、`retain_back`は直近の要素を残す操作に直接対応します。 残す個数を調整するためだけにpredicateへ外部のcounterを持たせる必要がなくなります。

`retain`は、各要素について残すかを判定する操作です。 たとえば値が条件を満たす要素だけを残す処理と、直近2件を残す処理では、削除する条件が違います。 新APIの`len`は条件に合う要素数の上限ではなく、末尾から保存する個数です。 `retain_back(0)`なら全要素を削除し、現在の長さ以上を指定した場合はすべての要素を残します（[retain\_backの境界条件](https://doc.rust-lang.org/std/collections/struct.VecDeque.html#method.retain%5Fback)）。

末尾を残した後は、論理的なindexも残った要素に対して振り直されます。 `tail`の操作前のindex 2にある30は、`retain_back(2)`の後でindex 0になります。 長さ2へ短縮する処理は、元のindexを保ったまま前半を空欄にする処理ではありません。 外部に元のindexを保存するAPIへ組み込む場合、保存したindexが短縮後にも同じ値を指すとは扱えません。

### `Box`に入った配列を移動しながら反復する

`IntoIterator`の追加は、配列を持つ`Box`を所有した反復と、参照を借りた反復の両方に関係します。 `&mut Box<[String; 2]>`の反復は各文字列を変更でき、所有した`Box`の反復は文字列自体を移動できます（[Box<\[T; N\]>のIntoIterator](https://doc.rust-lang.org/std/boxed/struct.Box.html#impl-IntoIterator-for-Box%3C%5BT;+N%5D,+A%3E)）。

```rust
let mut array = Box::new([String::from("a"), String::from("b")]);
for value in &mut array {
    value.push('!');
}
let moved: Vec<String> = array.into_iter().collect();
assert_eq!(moved, ["a!", "b!"]);

```

借用した反復なら、元の`Box`を後から使えます。 所有した反復なら、元の`Box`を消費し、要素の所有権をiteratorへ移します。 要素が`Copy`でない`String`でも、所有権を持つ経路なので値をcloneする必要はありません。 配列をheap上に置いたまま受け渡すAPIで、配列の要素を最後に消費する処理まで同じ型からつなげられます。

反復の途中でiteratorを破棄した場合は、取り出していない要素をiterator側が管理します。 元の`Box`を消費した後にも、呼び出し元が元の`Box`を別途解放する処理を持つ構成にはしません。 `Vec::into_parts`とは異なり、利用者がrawな管理情報を受け取るAPIではないためです。 同じ所有権の移動でも、safeなiteratorとして要素を消費する場合と、raw pointerで管理を引き継ぐ場合とで、呼び出し側に残る責任が変わります（[IntoIteratorの契約](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html)）。

文字列では、`Vec<u8>`を消費する`String::from_utf8_lossy_owned`と、`FromUtf8Error::into_utf8_lossy`が加わりました。 不正なUTF-8の部分は、replacement characterへ置き換えます。

```rust
assert_eq!(String::from_utf8_lossy_owned(vec![65, 255, 66]), "A�B");

```

所有しているbyte列を受け取るため、有効なUTF-8なら元のallocationを利用できます（[String::from\_utf8\_lossy\_owned](https://doc.rust-lang.org/stable/std/string/struct.String.html#method.from%5Futf8%5Flossy%5Fowned)）。

### 有効な入力のallocationを引き継ぎ、不正な入力では内容を変換する

借用した`&[u8]`を受け取るlossy変換の結果は、入力の借用か、新しい文字列になります。 所有した`Vec<u8>`を受け取る新APIでは、入力のallocationを所有したまま変換を進められます。 有効なUTF-8では、入力と結果の先頭pointerが一致します。

```rust
let bytes = b"valid".to_vec();
let address = bytes.as_ptr();
let text = String::from_utf8_lossy_owned(bytes);
assert_eq!(address, text.as_ptr());

```

不正なUTF-8を含む入力では、結果の文字列のbyte数が元の長さと一致しない場合があります。 `[65, 255, 66]`は3 byteですが、置換後の`A�B`はUTF-8で5 byteです。 `A`と`B`が1 byteずつ、replacement characterが3 byteになるためです。 所有したbufferを受け取るAPIでも、元のcapacityで必ず変換を完了できるとは限りません。

lossy変換では、不正なbyteを文字として保持するのではなく、置換した文字列を作ります。 したがって、結果の`String`を再びbyte列へ戻しても、元の255というbyteは復元できません。 `from_utf8`が返したエラーを記録して元のbyte列を保ちたい経路と、文字列として処理を続ける経路では、保持する情報が変わります。 新APIは後者を所有した入力から直接行うための追加です（[FromUtf8Error::into\_utf8\_lossy](https://doc.rust-lang.org/std/string/struct.FromUtf8Error.html#method.into%5Futf8%5Flossy)）。

`String::from_utf8`は最初の不正なbyteの位置を返し、errorからlossy変換へ進めます。

```rust
let error = String::from_utf8(vec![65, 255, 66]).unwrap_err();
assert_eq!(error.utf8_error().valid_up_to(), 1);
assert_eq!(error.into_utf8_lossy(), "A�B");

```

エラーからbyte列を取り出し、借用のlossy変換を呼び、さらに所有した文字列へ変換する操作を、エラーを消費する呼び出しへまとめられます。

正常な入力だけを受け付ける契約なら、`from_utf8`の成功と失敗を区別する意味は残ります。 lossy版へ置き換えると、以前は失敗していた入力を、内容の変わった文字列として受け付ける動作になります。 既存の呼び出しを新しい所有版へ移す場合にも、allocationの扱いだけでなく、不正な入力を拒否するのか置換するのかを維持します。 文字列としての妥当性を回復することと、入力の情報を完全に保つことは異なる結果です。

ほかにも、`Box<[T; N]>`と参照に対する`IntoIterator`、`StepBy<I>`の`FusedIterator`実装が追加されています。 時刻設定には`fs::set_times`と`fs::set_times_nofollow`が加わりました。 後者はsymbolic linkの参照先をたどらず、link自体の時刻を変更します（[1.99.0のAPI追加](https://doc.rust-lang.org/stable/releases.html#version-1990-2026-10-01)）。

## Cargoの既定動作と互換性への影響

Cargoには`debug` profileが追加され、現時点では`dev`と同じ設定になります。 CI環境ではincremental compilationが既定で無効になりました。 edition 2024では、継承した`default-features = true`を上書きできます。 member側で`default-features = false`を指定します。 edition 2024で従来は拒否されていた組み合わせを受け付けるため、workspace側の既定値を残したままmemberだけで無効化できます（[Cargoを含む1.99.0の変更履歴](https://doc.rust-lang.org/stable/releases.html#cargo)）。

### workspaceからの継承と、dependency graph全体のfeature解決

`default-features`の継承では、同じdependencyを指すmanifestの設定を採用する優先順位が変わりました。 edition 2024以降では、member側に値があればmember側を使い、なければworkspace側の値を使います。 両方で省略した場合は、通常のdependencyと同じく有効になります。 したがって、workspace側が`true`でも、member側の`false`によって当該dependency経路のdefault featureを外せます。 個別のmemberだけが小さい機能集合を必要とする場合に、workspace全体の既定値を変更せず指定できます（[RFC 3945の設定の優先順位](https://github.com/rust-lang/rfcs/blob/master/text/3945-inherit-default-features.md#reference-level-explanation)）。

`dep`の`rich`をdefault featureへ含めたworkspaceを考えます。

```toml
[features]
default = ["rich"]
rich = []

```

`off`はworkspaceから継承しながらdefaultを無効にし、`on`は継承した既定値を使います。 `dep::rich_enabled()`は`cfg!(feature = "rich")`を返し、build時に選択したfeatureをbooleanへ変換します。

```sh
cargo run -p off
# => rich=false
cargo run -p on
# => rich=true

```

同じdependencyのsourceを継承した2個のmemberでも、個別に選択したbuildでは異なるfeature集合になります。

```toml
[workspace]
members = ["dep", "off", "on"]
resolver = "3"

[workspace.dependencies]
dep = { path = "dep", default-features = true }

```

`off`のmanifestでは、`[dependencies]`へ`dep = { workspace = true, default-features = false }`を記述しています。 ただし、`false`はdependency graph全体からfeatureを禁止する指定ではありません。 `off`と`on`を一緒にbuildすると、`off`側でも`rich`が有効になります。

```sh
cargo build --workspace --target-dir target
./target/debug/off
# => rich=true

```

`on`の経路が同じ`dep`のdefault featureを要求し、feature unificationで要求を合わせたためです。 manifestの継承で採用する値と、複数のdependency経路から集まったfeature要求は、異なる段階で処理されます（[Cargoのfeature unification](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification)）。

`default-features = false`を追加しても、別のmemberやdependencyから同じfeatureへの要求があれば有効になります。 たとえば`no_std`向けmemberが単体ではbuildできても、別のmemberを同時に選択した結果、`std`を使うfeatureが加わる構成はあり得ます。 新しい継承規則は、memberがdependencyを宣言する自由を増やしますが、featureを足し合わせるCargoの規則を、featureの減算へ変更してはいません。 不要な機能を確実に含めないbuildでは、実行するcommandが選択するpackage集合まで入力の一部になります。

resolverには、build-dependencyと通常のdependency、対象外のplatformのdependencyなどで、featureを不用意に共有しない規則もあります。 dependencyを分離する条件では、同じversionのcrateが異なるfeature集合で複数回buildされることもあります。 したがって、workspace内に同じcrate名が現れるという理由だけで、すべてのbuild単位のfeatureが同じになるとも限りません。 `off`と`on`からの通常のdependencyを同じtargetでbuildすると、`dep`へのfeature要求が合流します（[resolver version 2以降の分離条件](https://doc.rust-lang.org/cargo/reference/features.html#feature-resolver-version-2)）。

### `debug` profileという名前と、debug情報の設定

`cargo build --profile debug`は1.99で組み込みprofileとして受け付けられます。 Cargoのbuild完了表示にも、選択したprofile名が現れます。

```sh
cargo build --profile debug
# => Finished `debug` profile [unoptimized + debuginfo] ...

```

1.99時点の`debug`と`dev`の既定設定は同じです。 新しい名前を選んだだけで、実行時の最適化やdebug情報の量が従来の`dev`から変わるわけではありません。 Cargo側は、将来`dev`を短い開発反復向けに調整する準備として、debugging向けの名前を分けています（[debug profileを追加したPR #17214](https://github.com/rust-lang/cargo/pull/17214)）。

profile名の`debug`と、profile内の`debug`という設定は別の対象です。 後者は`rustc`へ渡すdebug情報の量を制御します。 たとえば`line-tables-only`はbacktraceへfile名や行番号を残す用途を持ちますが、変数やparameterの情報までは含めません。 最適化の程度は`opt-level`であり、整数overflowの実行時検査は`overflow-checks`です。 profile名を変える操作だけでは、各設定を個別に変更した結果を代替しません（[Cargoのdebug情報と最適化設定](https://doc.rust-lang.org/cargo/reference/profiles.html#debug)）。

workspaceのprofile設定は、rootの`Cargo.toml`から読み込まれます。 dependencyのmanifestへprofileを書いても、利用側workspaceのbuild設定を強制できません。 たとえばlibrary側でdebug情報を省略する設定を持っていても、利用側のrootで選ぶ設定がbuildに適用されます。 新しい`debug` profileも、memberごとに分散せず、rootのmanifestへ設定します（[profile設定を読むmanifestの範囲](https://doc.rust-lang.org/cargo/reference/profiles.html)）。

### CIではincremental compilationの保存先を使う前提が変わる

CIの既定値を変えた理由は、incremental compilationの状態を保存する処理にも時間と容量が必要だからです。 次回のbuildで状態を再利用しないrunnerでは、状態のserializationとdiskへの書き込みが、再利用による短縮へつながりません。 再利用するrunnerでも、増えたcacheを圧縮し、networkで転送する処理が追加されます。 Cargoの変更は、CIでの再利用率が低い場合にまで、incremental compilationを既定で有効にする構成をやめたものです（[CIの既定値を変更したPR #17220](https://github.com/rust-lang/cargo/pull/17220)）。

CIの判定には`CI`環境変数を使います。 設定の優先順位では、`CARGO_INCREMENTAL`と`build.incremental`がCIの判定より上にあります。 したがって、継続して同じbuild状態を使う専用runnerなら、明示した設定で有効にする経路は残ります。 逆に、profileの既定値だけへ依存していたCIでは、1.99への更新によってincremental用の状態を生成しなくなる場合があります。 変更の対象はcompilerの中間状態であり、`Cargo.lock`を無視したり、dependencyのversion解決を変更したりする機能ではありません。

同じCIであっても、incrementalの保存量が減ったことと、build全体が速くなったことは同じ測定ではありません。 Cargoが変更したのは、再利用の見込みが不明な環境で選ぶ既定値です。 既存のCI設定で`CARGO_INCREMENTAL=0`を指定していた場合は、既定値の変更前から無効だったので挙動は変わりません。

compiler側では、generic itemへの`no_mangle`がhard errorになり、`std::i32::MAX`のような旧形式の整数定数はdeprecatedになりました。 整数定数には`i32::MAX`のように型に属する定数を使えます。 `transmute_copy`は`?Sized`に対応し、失敗時のpanicはunwindしなくなっています（[1.99.0の互換性に関する変更](https://doc.rust-lang.org/stable/releases.html#compatibility-notes)）。

`Pin::new_unchecked`では、pointerに実装するsafe traitも安全性の条件に関わります。 `Deref`や`DerefMut`は常に同じobjectを指し、pointerの破棄時にも中身を移動させない必要があります。 `Clone`による複製先も、`Pin`として安全に扱えるpointerでなければなりません（[安全性の条件を変更したPR #156935](https://github.com/rust-lang/rust/pull/156935)）。

使い切った`RangeInclusive`の`start`と`end`は、特定の値になる保証がありません。 反復終了後の境界値を読み、終了前の範囲や最後の値を復元する前提にはできません（[標準libraryの変更](https://doc.rust-lang.org/stable/releases.html#libraries)）。

### `Pin`の保証をpointer側のsafeな操作でも壊さない

`Pin<P>`は、pointer型`P`の操作がpointeeを移動させないという条件にも依存します。 `Pin::new_unchecked`を呼ぶ時点で、単にpointerが有効なallocationを指していれば済むわけではありません。 `P`の`Deref`が呼ぶたびに異なるobjectを返す実装なら、固定したつもりのobjectと、後で参照するobjectが一致しなくなります。 `DerefMut`や`Drop`がpointeeを移動させる実装も、固定した位置へ依存する値の前提を壊します（[Pin::new\_uncheckedのSafety](https://doc.rust-lang.org/std/pin/struct.Pin.html#method.new%5Funchecked)）。

`Clone`の条件は、複製したpointerを再び`Pin`として使う場面に関わります。 複製先が、元のpointeeを移動するような所有者へ変わる実装であれば、複製する操作はsafeでも、構築した`Pin`の保証は維持できません。 1.99の修正では、safe traitの実装も`Pin`を成立させる契約の一部である点を明確にしました。 標準のpointerと独自のpointerを同一視せず、`Pin`を作るunsafeな境界で、後から呼べる操作まで保証に含めます（[契約を整理したPR #156935](https://github.com/rust-lang/rust/pull/156935)）。

`Pin`は、pointeeの中の任意のfieldをすべて自動的に固定する構文でもありません。 固定の保証を引き継ぐstruct fieldの範囲は、projectionを公開する側の設計に依存します。 今回のpointerに関する条件を満たしても、別のunsafeなprojectionでfieldを移動させれば、field側の保証は壊れます。 反対に、`Unpin`な値は移動を禁止する必要がないため、すべての`Pin`利用に同じ制約を追加する説明も正確ではありません（[Pinのstructural pinning](https://doc.rust-lang.org/std/pin/index.html#projections-and-structural-pinning)）。

### 使用済みの範囲と、宣言した上限を同じ状態へ保存しない

`RangeInclusive`は、両端を保持する値であると同時に、反復すると内部状態が進むiteratorです。 たとえば上限へ到達した後の値から`start()`を読んで、最初に指定した下限を復元する使い方は、もともとの入力を別に保持する実装とは異なります。 反復の途中では残っている範囲を表し、反復後は使い切った状態になります。 1.99では反復処理を最適化した結果、使い切った後の境界値が以前と異なる場合があります（[RangeInclusiveの終了後の状態](https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html)）。

`is_empty()`とiteratorとしての終了状態を扱うこと、使い切った後の特定の境界値を期待することは別の契約です。 最初の下限や上限を後で記録する処理なら、反復前に元の値を保存した実装は、終了後の内部表現に依存しません。 sliceのindexとして使い切った範囲を再利用していた処理では、反復前の範囲と同じ対象を指すと仮定できなくなります。 最適化で変更されたのは、保証のなかった終了後の境界値です。 `a..=b`の範囲が上限を含む規則は変わりません。

## pathから時刻を設定し、symbolic linkの追跡を選ぶ

`File::set_times`は、すでに開いているfileへ時刻を設定するAPIです。 1.99で加わった`fs::set_times`はpathを受け取り、対象のfileやdirectoryへ時刻を設定します。 `fs::set_times_nofollow`は、pathの最後がsymbolic linkなら、参照先ではなくlink自身へ適用します。 たとえばarchiveからlinkを復元する処理では、参照先の更新時刻を変えず、linkの時刻を復元する操作を選べます（[set\_times](https://doc.rust-lang.org/std/fs/fn.set%5Ftimes.html)、[set\_times\_nofollow](https://doc.rust-lang.org/std/fs/fn.set%5Ftimes%5Fnofollow.html)）。

時刻の指定には`FileTimes`を使います。 `FileTimes::new()`では変更する時刻が指定されておらず、`set_modified`だけを呼べば更新時刻を選んだ値にします。 最終アクセス時刻まで同じ値にしたい場合は、`set_accessed`も明示して組み立てます。 更新時刻を設定するためにfileの内容を書き直す操作ではないので、内容のbyte列と、変更するmetadataを別に扱えます（[FileTimesの各fieldの指定](https://doc.rust-lang.org/std/fs/struct.FileTimes.html)）。

darwin/aarch64では、同じsymbolic linkのpathから、参照先とlink自身の更新時刻を別々に設定できます。 参照先の時刻は`metadata`、link自身の時刻は`symlink_metadata`で取得します。

```rust
use std::fs::{self, FileTimes};
use std::time::{Duration, UNIX_EPOCH};

fn main() -> std::io::Result<()> {
    let root = std::env::temp_dir().join(format!("rust199-times-{}", std::process::id()));
    fs::create_dir(&root)?;
    let target = root.join("target");
    let link = root.join("link");
    fs::write(&target, b"content")?;
    std::os::unix::fs::symlink(&target, &link)?;
    let first = UNIX_EPOCH + Duration::from_secs(86_400);
    let second = UNIX_EPOCH + Duration::from_secs(172_800);
    fs::set_times(&link, FileTimes::new().set_modified(first))?;
    fs::set_times_nofollow(&link, FileTimes::new().set_modified(second))?;
    assert_eq!(fs::metadata(&target)?.modified()?, first);
    assert_eq!(fs::symlink_metadata(&link)?.modified()?, second);
    assert_eq!(fs::read_to_string(&target)?, "content");
    fs::remove_file(link)?;
    fs::remove_file(target)?;
    fs::remove_dir(root)
}

```

新しいAPIは、pathへ指定した対象に対してOSの時刻設定機能を呼び出します。 Unixでは`AT_SYMLINK_NOFOLLOW`を指定した`utimensat`、Appleでは`FSOPT_NOFOLLOW`を指定した`setattrlist`に対応します。 Windowsでの現在の実装は`SetFileTime`に対応します。 権限が足りない場合やOSで操作を支えていない場合にはerrorを返します。 時刻設定を無視して成功するAPIではありません（[set\_times\_nofollowのplatform別の動作とerror](https://doc.rust-lang.org/std/fs/fn.set%5Ftimes%5Fnofollow.html#platform-specific-behavior)）。

時刻の解像度はfile systemに依存するため、任意の細かい値を指定すれば必ず同じ精度で保存されるとまでは言えません。 比較するtimestampは秒単位で、file systemが保持する精度を越える小数部は含みません。 APIのlink追跡の有無と、保存できる時刻の精度は別の条件です。 開いたhandleが表すobjectへ設定する処理と、pathを再解決して設定する処理では、対象を確定する時点が異なります。 解決するまでにpathの指す対象が変われば、設定先のobjectも変わり得ます。

## compilerの診断で変更になる入力

`no_mangle`は、外部から参照するsymbol名をRustの通常のmanglingから外す属性です。 generic functionは、具体的な型やconst argumentごとに実装を生成するため、複数の実装へ同じ固定symbol名を与える契約とは合いません。 1.99では、generic itemへの`no_mangle`がwarningからhard errorになりました。 `identity<T>`は具体化した型ごとにsymbol名を分ける必要があるため、`#[unsafe(no_mangle)]`自体をerrorにします。 診断は、型またはconstのgeneric functionにmanglingを要求します。

```rust
#[unsafe(no_mangle)]
pub fn identity<T>(value: T) -> T { value }
// error: functions generic over types or consts must be mangled

```

呼び出しを`identity(1_u32)`の1個に限っても、generic itemへ付けた属性は許可されません（[hard error化したPR #154585](https://github.com/rust-lang/rust/pull/154585)）。

FFIへ公開するentry pointが固定された型を受けるなら、genericな内部実装と、具体的な型を持つ外部向けfunctionを分けられます。 属性を削除するだけで外部の呼び出し元が同じsymbolを発見できるようになるわけではありません。 外部向けの名前が必要な場所を、具体的なABIを持つfunctionへ限定する変更になります。 外部へ公開する実装を型と名前で確定させ、genericな内部実装を呼び出すwrapperを分けます。

`unconditional_panic`の診断対象には、sizeが0だと必ずpanicする`chunks`や`windows`の呼び出しも加わりました。 sliceを空にする操作と、chunkの大きさを0にする操作は異なります。 空のsliceをsize 1で分割する呼び出しなら、単に要素がない結果ですが、size 0は分割方法として許可されません。 1.99では、明らかにsizeが0になる呼び出しを、panicへ到達する前に診断できる範囲が広がっています（[unconditional\_panicを拡張したPR #153563](https://github.com/rust-lang/rust/pull/153563)）。

旧形式の整数moduleのdeprecated化は、定数の数値を変える変更ではありません。 `std::i32::MAX`という経路から、整数型自身のassociated constantである`i32::MAX`へ移します。 型のassociated constantを選ぶことで、旧moduleをimportして定数を参照する形を減らせます。 `deny(warnings)`を設定するbuildでは、deprecatedの診断でbuildが失敗します。 数値の変更を伴わなくても、参照経路を修正する必要があります（[std::i32のdeprecated属性](https://doc.rust-lang.org/std/i32/index.html)）。

## 1.99.1で修正されたABIと配布物

1.99.1では、x86\_64でCへ`bool`を返す際のzero extensionが復旧しました。 1.99.0では戻り値のbit 1からbit 7が未規定になり、CのABIが求める値の表現を満たしていませんでした。 FFIの戻り値の表現に関わり、Rustの型検査では取り除けないregressionでした（[1.99.1のbool ABI修正](https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/)）。

残る2件は、LLVMのloop idiom認識で必要になった`wcslen`のfallbackと、macOS配布物の`rust-lld`です。 `wcslen`の欠落はUEFI向けの`std`などに影響していました。 macOSでは、`rust-lld`の直接起動時に`libLLVM`を発見できない場合がありました。 rustup proxyを経由しない起動を修正するため、rpathが追加されています（[1.99.1の修正内容](https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/)）。

`bool`のregressionでは、Rust側のtrueとfalseの意味を変更したのではなく、返り値をABI上のbit列へ変換する処理が欠けていました。 x86\_64の当該規約では、少なくとも低位8 bitのうちbit 1からbit 7を0にする必要があります。 bit 0だけが論理値に合っていても、呼び出し元が低位8 bitを返り値として使えば、期待する0または1と一致しない値になります。 Rustのfunction本文が正しくても、呼び出し境界の命令がABIを満たさなければ、外部のcodeへ不正な値を返します（[ABI不一致を報告したissue #163911](https://github.com/rust-lang/rust/issues/163911)）。

`wcslen`の欠落では、元のRustコードが`wcslen`を直接呼んでいなくてもlink errorになり得ました。 LLVMが文字列の長さを数えるloopを認識し、library functionへの呼び出しへ置き換えるためです。 UEFIの報告では、release buildのlink時に`undefined symbol: wcslen`が現れています。 compilerが生成した呼び出しを受けるfallbackをtarget側に用意する必要があります。 fallbackの欠落は、型検査やobject fileの生成後、link段階の失敗として現れます（[UEFIでのlink errorを報告したissue #163614](https://github.com/rust-lang/rust/issues/163614)）。

macOSの`rust-lld`は、tool自身が必要とする`libLLVM`の探索経路に問題がありました。 rustup proxyを経由した起動では環境によってlibraryを発見できても、toolchain内のcompilerを直接起動すると探索に失敗する場合がありました。 修正版は配布するtoolのrpathを補い、`rust-lld`が自身の依存libraryを見つける経路を持たせています。 アプリケーション自身がlinkするlibraryを追加する修正とは異なり、linkerを起動するまでの配布物の依存関係に関わる修正です（[macOS配布物を修正したPR #163954](https://github.com/rust-lang/rust/pull/163954)）。