こんにちは、あずんひ(@aznhe21)です。この歳になってついに運転免許の取得を決意しました。
さて、本日11/4(金)にRust 1.65がリリースされました。 この記事ではRust 1.65での変更点を詳しく紹介します。 もしこの記事が参考になれば、記事末尾から活動を支援頂けると嬉しいです。
- ピックアップ
- 安定化されたAPIのドキュメント
- core::ops::Bound::as_ref
- core::pointer::cast_mut
- core::pointer::cast_const
- std::backtrace
- std::backtrace::Backtrace
- std::backtrace::Backtrace::capture
- std::backtrace::Backtrace::force_capture
- std::backtrace::Backtrace::disabled
- std::backtrace::Backtrace::status
- std::backtrace::BacktraceStatus
- std::io::read_to_string
- 変更点リスト
- 関連リンク
- さいごに
- ライセンス表記
ピックアップ
個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。
関連型でジェネリクスが使えるようになった
皆さん待望のGATs(Generic Associated Types)と呼ばれる機能が使えるようになりました。 これはトレイトの関連型でジェネリクスが使える機能でトレイトの自由度が格段に上がります。 ただし現在の実装には後述するように多くの制限があることに注意してください。
trait Trait { // 普通の関連型 type A; // ジェネリクスを使った関連型 type B<T>; // ジェネリクスと境界を使った関連型 type C<T> where T: std::ops::Add; // もちろんライフタイムや定数もOK type D<'a>; type E<const N: usize>; }
下記の例では関連型Item
にライフタイムを要求するイテレーターを定義しています。
このイテレーターでは要素を保持したまま次の要素に進むことはできません。
これは、ファイルを段階的に読みながら進むような場合に間違った使い方を抑制することができます。
trait LendingIterator { type Item<'a> where Self: 'a; fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>; } // cannot borrow `iter` as mutable more than once at a time fn hoge<I: LendingIterator>(mut iter: I) { let x = iter.next().unwrap(); // ----------- first mutable borrow occurs here let y = iter.next().unwrap(); // ^^^^^^^^^^^ second mutable borrow occurs here }
tarクレートのArchive::entries
ではイテレーターを進めたあとで前の要素を扱うとデータが壊れるとの注意書きがされていますが、
代わりにLendingIterator
を用いることでこの罠を回避することが出来るでしょう。
なお、現在のボローチェッカーではこのLendingIterator
にはfilter
メソッドを定義できないなどの問題があります。
他にもGATsにはオブジェクト安全ではない(&dyn Trait
にできない)問題やwhere Self: 'a
が毎回必要で鬱陶しいといった問題(?)もあります。
これらは将来的には改善されていく予定です。
詳細は、英語ですがGeneric associated types to be stable in Rust 1.65や
The push for GATs stabilizationなどをご覧ください。
ちなみに、将来的にはGATsを糖衣構文とすることでトレイトでの非同期関数が使えるようになる予定です。
letによる束縛でパターンとelseを書けるようになった
let
による束縛時、左辺には変数名の代わりにパターンを、右辺のあとにelse
を書くことで
「パターンにマッチしなかった場合」の処理が書けるようになりました。
Swift言語が分かる方向けに言えばguard let
文と概ね同じと言えます。
例えばOption<T>
の値がSome
なら中身を取り出しNone
ならcontinue
することを考えます。
Rust 1.64まではこの様に4行必要だったのが・・・
let value = match value { Some(v) => v, _ => continue, }
Rust 1.65からはこの様に1行で書けるようになります。
let Some(value) = value else { continue };
なお、このelse
ブロックは!
型が返るのを要求するため、return
やpanic!()
などによりelse
ブロックの処理を終わらせる必要があります。
// OK let 1 = 1 else { return }; let 1 = 1 else { panic!() }; let 1 = 1 else { std::process::exit(0) }; // NG: `else` clause of `let...else` does not diverge let 1 = 1 else { 1 }; // ^^^^^ expected `!`, found integer
また、else
が不要な場合(反駁不能=irrefutableなパターン)では警告が発されます。
// warning: irrefutable `let...else` pattern let x = true else { return };
このlet-else
はPerlやRubyなどにおけるunless
文のように使うこともできますが、混乱するだけなのでやめておいた方が良いでしょう
(そもそもlet-else
はパターンマッチであり比較ではない)。
let x = 0; // これと・・・ if x != 0 { return; } // これは同じように動く(ただしlet-elseでは所有権を奪うことがある) let 0 = x else { return };
名前付きブロックにより処理途中で抜けられるようになった
Rust 1.65ではブロックに名前を付けられるようになり、break
に名前を指定することでそのブロックを抜けられるようになりました。
// 普通のブロック。変数の寿命を短くするのに使われることが多い { // このvはブロック内でしか生きられない let v = vec![0, 1, 2]; } // ライフタイムと同じ記法でブロックに名前を付ける 'x: { break 'x; // breakに名前を指定してブロックから抜ける }
またbreak
に値を指定することで、ブロック末尾から値を返すのと同じように、ブロックを抜ける際に値を返すことができます。
// 普通のブロックでは末尾の式から値が返される let a: usize = { let v = vec![1, 2, 3]; v.iter().copied().sum() }; // 名前付きブロックではbreak時に値を指定することでも値を返せる let b: usize = 'b: { let v = vec![1, 2, 3]; if rand::random() { break 'b 0; } v.iter().copied().product() };
メソッドチェーンをコネコネする代わりに使うと便利かもしれません。
// あなたはどっち派? // メソッドチェーン派 let x = std::fs::read_to_string("hoge.txt") .ok() .and_then(|value| value.parse().ok()) .unwrap_or(0); // 名前付きブロック派 let x = 'x: { let Ok(value) = std::fs::read_to_string("hoge.txt") else { break 'x None }; let Ok(value) = value.parse() else { break 'x None }; Some(value) }.unwrap_or(0);
バックトレースの取得・管理ができるようになった
Rust 1.65ではstd::backtrace
モジュールとその中の型が使えるようになり、
バックトレースの取得・管理ができるようになりました。
現時点ではバックトレースの取得と全体の表示のみができます。
バックトレースの取得というのは非常に遅い処理であるため、
標準APIであるBacktrace::capture
は初期状態では何も取得しません。
環境変数RUST_LIB_BACKTRACE
かRUST_BACKTRACE
に0
以外の値を設定することでバックトレースを取得できるようになります。
また、Backtrace::force_capture
を使うことで強制的に取得することもできます。
// 環境変数によって動作が変わる let backtrace = std::backtrace::Backtrace::capture(); println!("{backtrace:#?}"); // RUST_LIB_BACKTRACEが未設定では<disabled> // 環境変数に関わらず常に取得 let backtrace = std::backtrace::Backtrace::force_capture(); println!("{backtrace:#?}"); // Backtrace [ // { fn: "hoge::main", file: "./src/main.rs", line: 7 }, // // ごちゃごちゃ // { fn: "main" }, // { fn: "__libc_start_main" }, // { fn: "_start" }, // ]
多くの言語ではエラー型にバックトレースが含まれており、エラー発生場所が分かることによってデバッグを支援しています。
Rustでもanyhowクレートのbacktrace
機能を有効にすることで、
Error
オブジェクトにエラー生成場所のバックトレースが含まれるようになります。
なお、これは「anyhowクレートのError
オブジェクトが生成された場所」であり、
std::io::Error
など大元のエラーが発生した場所ではないことに注意してください。
// anyhow = { version = "1.0.66", features = ["backtrace"] } let e = anyhow::Error::msg("hoge"); println!("{e:?}"); // `RUST_LIB_BACKTRACE=1 cargo run`で実行するとこの様に出力される // hoge // // Stack backtrace: // 0: hoge::main // at ./src/main.rs:2:13 // ごちゃごちゃ // 6: main // 7: <unknown> // 8: __libc_start_main // 9: _start // at /build/glibc/src/glibc/csu/../sysdeps/x86_64/start.S:115
ただし、現時点ではanyhowクレートがバックトレースの取得に使用しているのは標準ライブラリではなくbacktraceクレートです。 実は標準ライブラリも裏ではこのbacktraceクレートを使用してはいるものの、 Cargoからこのクレートを使えば標準ライブラリと二重にbacktraceクレートが使用されてしまうことになるので、 出来ればanyhowクレートも標準ライブラリを使用するようにして欲しいものです。 なお環境変数による切り替えの仕様は標準ライブラリ、anyhowクレートともに共通しているため、動作に違いはありません。
また、JavaにはThrowable#getStackTrace()
があるように、多くの言語にはエラー型からバックトレースを取得するAPIがあります。
Rustにも標準のエラーAPIであるError
トレイトがありますが、バックトレースを取得する関数は現時点ではありません。
以前のNightlyにはbacktrace
関数がありましたが、
現在はバックトレースのみならずあらゆる型が取得できるAPIとして策定中です。
RLSの終焉
Rust用LSP実装のRLSがrustのソースツリーから削除されました。 まだ使用している人はrust-analyzerに移行しましょう。
rustupでRLSをインストールしている場合、rlsバイナリは依然として残り続けます。 ただしこれは言語サーバーとしては実質的に機能はせず、起動時にrust-analyzerへの移行を促す文言が表示されるだけの小さなプログラムに置き換わっています。
安定化されたAPIのドキュメント
安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。
core::ops::Bound::as_ref
impl<T> Bound<T> { #[inline] #[stable(feature = "bound_as_ref_shared", since = "1.65.0")] pub fn as_ref(&self) -> Bound<&T> { /* 実装は省略 */ } }
&Bound<T>
からBound<&T>
に変換する。
core::pointer::cast_mut
impl<T: ?Sized> *const T { #[stable(feature = "ptr_const_cast", since = "1.65.0")] #[rustc_const_stable(feature = "ptr_const_cast", since = "1.65.0")] pub const fn cast_mut(self) -> *mut T { /* 実装は省略 */ } }
型を変えることなく定数性を変える。
コードをリファクタリングしても暗黙的に型を変えることがない分、as
より少し安全である。
core::pointer::cast_const
impl<T: ?Sized> *mut T { #[stable(feature = "ptr_const_cast", since = "1.65.0")] #[rustc_const_stable(feature = "ptr_const_cast", since = "1.65.0")] pub const fn cast_const(self) -> *const T { /* 実装は省略 */ } }
型を変えることなく定数性を変える。
コードをリファクタリングしても暗黙的に型を変えることがない分、as
より少し安全である。
厳密には必要ではない(*mut T
は*const T
に型強制される)ものの、
これは*const T
におけるcast_mut
との対称性のために提供されていて、
暗黙の型強制の代わりに使うことで文書的意味を持つことがある。
std::backtrace
OSスレッドにおけるバックトレースの取得への対応。
このモジュールには、OSスレッド自体から、実行中のOSスレッドにおけるバックトレースを取得するための必要な対応が含まれる。
Backtrace
型は関数Backtrace::capture
およびBacktrace::force_capture
によりスタックトレースの取得に対応する。
バックトレースは、エラー(std::error::Error
を実装するような型など)と紐付け
エラー発生場所からの因果関係の連鎖を調べるのに非常に便利である。
正確さ
バックトレースはできるだけ正確になるよう努力されているものの、 バックトレースの完全な正確さに保証はない。 命令ポインタ、シンボル名、ファイル名、行番号などの情報全ては間違いである可能性がある。 とは言え正確さはできるだけ高くなるよう追求されており、また、バグは改善場所を示すためいつでも重宝されるだろう。
ほとんどのプラットフォームでは、バックトレースにファイル名と行番号を出力するためには プログラムをデバッグ情報を含めてコンパイルする必要がある。 デバッグ情報を含めない場合、ファイル名と行番号は出力されないだろう。
プラットフォームの対応
libstdがコンパイルできるすべてのプラットフォームがバックトレースの取得に対応しているわけではない。
一部のプラットフォームでは、バックトレースを取得しようとしても特に何も起こらない。
プラットフォームがバックトレースの取得に対応しているかを確認するには、Backtrace::status
の戻り値である列挙型BacktraceStatus
を調べられたい。
前述のように、プラットフォームの対応における正確さはできるだけ追求されている。 場合により、実行時にライブラリが使用できない、あるいは何らかの原因によりバックトレースの取得ができないことがある。 プラットフォームでバックトレースが取得できない問題がある場合はぜひ報告されたい。
環境変数
初期状態では、Backtrace::capture
関数は実際にはバックトレースを取得しない。
この動作は次に述べるように2つの環境変数によって制御される。
RUST_LIB_BACKTRACE
:この値が0
に設定されている場合、Backtrace::capture
がバックトレースを取得することはない。 これ以外の値が設定されている場合はBacktrace::capture
は有効化される。RUST_BACKTRACE
:RUST_LIB_BACKTRACE
が設定されていない場合、この変数はRUST_LIB_BACKTRACE
と同じルールで考慮に入れられる。上記の環境変数のどちらも設定されていない場合、
Backtrace::capture
は無効化される。
バックトレースの取得は非常に実行コストが高い操作であるため、 環境変数により、この実行時の性能低下を強制的に無効化する、または一部のプログラムでは選択的に有効化することができる。
Backtrace::force_capture
関数を使ってこれらの環境変数を無視することができることに注意されたい。
また、環境変数の状態は最初のバックトレースが生成される際にキャッシュされるため、
実行時にRUST_LIB_BACKTRACE
やRUST_BACKTRACE
を変更しても実際にはバックトレースの取得方法が変わらない可能性があることにも注意されたい。
std::backtrace::Backtrace
#[stable(feature = "backtrace", since = "1.65.0")] #[must_use] pub struct Backtrace { /* フィールドは省略 */ }
取得したOSスレッドのスタックバックトレース。
この型は取得した時点におけるOSスレッドのスタックバックトレースを表す。
設定のため、Backtrace
型は場合によっては内部的に空になることがある。
詳細はBacktrace::capture
を参照されたい。
std::backtrace::Backtrace::capture
impl Backtrace { #[stable(feature = "backtrace", since = "1.65.0")] #[inline(never)] // want to make sure there's a frame here to remove pub fn capture() -> Backtrace { /* 実装は省略 */ } }
現在のスレッドにおけるスタックバックトレースを取得する。
この関数は、現在実行中ののOSスレッドにおけるスタックバックトレースを取得し、
あとからスタックトレース全体を表示したり文字列に可視化したりできるBacktrace
型を返す。
バックトレース用変数RUST_BACKTRACE
とRUST_LIB_BACKTRACE
のどちらも設定されていない場合、この関数は何もしない。
どちらかの環境変数が設定・有効化されている場合、この関数は実際にバックトレースを取得する。
バックトレースの取得はメモリ消費が激しく、かつ遅い処理のため、
これらの環境変数によってBacktrace::capture
の使用が許可され、この環境変数が設定されている場合のみ速度の低下を招く。
環境変数に関わらず強制的にバックトレースを取得するにはBacktrace::force_capture
を使用すること。
std::backtrace::Backtrace::force_capture
impl Backtrace { #[stable(feature = "backtrace", since = "1.65.0")] #[inline(never)] // want to make sure there's a frame here to remove pub fn force_capture() -> Backtrace { /* 実装は省略 */ } }
環境変数の設定に関わらず、完全なバックトレースを強制的に取得する。
この関数の動作はcapture
と同じであるが、
環境変数RUST_BACKTRACE
とRUST_LIB_BACKTRACE
の値を無視して常にバックトレースを取得する点が異なる。
一部のプラットフォームではバックトレースの取得は高コストな操作であるため、性能を重視する部分では使用に注意されたい。
std::backtrace::Backtrace::disabled
impl Backtrace { #[stable(feature = "backtrace", since = "1.65.0")] #[rustc_const_stable(feature = "backtrace", since = "1.65.0")] pub const fn disabled() -> Backtrace { /* 実装は省略 */ } }
環境変数の設定に関わらず、無効化されたバックトレースを強制的に取得する。
std::backtrace::Backtrace::status
impl Backtrace { #[stable(feature = "backtrace", since = "1.65.0")] #[must_use] pub fn status(&self) -> BacktraceStatus { /* 実装は省略 */ } }
このバックトレースにおける、リクエストが対応されなかったか、無効化されているか、またはスタックレースが実際に取得されたかの状態を返す。
std::backtrace::BacktraceStatus
#[stable(feature = "backtrace", since = "1.65.0")] #[non_exhaustive] #[derive(Debug, PartialEq, Eq)] pub enum BacktraceStatus { #[stable(feature = "backtrace", since = "1.65.0")] Unsupported, #[stable(feature = "backtrace", since = "1.65.0")] Disabled, #[stable(feature = "backtrace", since = "1.65.0")] Captured, }
バックトレースの現在の状態。取得できたか、もしくは何らかの理由で空となっていることを示す。
バリアント(非網羅的)
この列挙型は非網羅的とされている
非網羅的な列挙型は将来的にバリアントが追加される可能性がある。
したがって、非網羅的な列挙型のバリアントをパターンマッチする際は、将来のバリアントを考慮し、別にワイルドカードのアームを追加する必要がある。
Unsupported
バックトレースの取得に対応していない。 これは現在のプラットフォーム向けに実装されていないためと考えられる。
Disabled
環境変数RUST_LIB_BACKTRACE
かRUST_BACKTRACE
によりバックトレースの取得が無効化されている。
Captured
バックトレースは取得され、そのBacktrace
は可視化時に適切な情報が出力されるはずである。
std::io::read_to_string
#[stable(feature = "io_read_to_string", since = "1.65.0")] pub fn read_to_string<R: Read>(mut reader: R) -> Result<String> { /* 実装は省略 */ }
これはRead::read_to_string
の手頃な関数である。
この関数を使用すると最初に変数を用意する必要が無くなり、エラーでない場合にのみバッファを使うことができるため、
型安全性が向上する
(Read::read_to_string
を使う場合は読み込みが成功したかどうかを確認する必要がある。
さもなければバッファは空になるか、あるいは部分的にしか満たされなくなる)。
性能
この関数は使いやすさと型安全性が向上する反面、性能制御が難しくなる。
例えば、String::with_capacity
とRead::read_to_string
を使うときのようにメモリを事前確保することはできない。
また、読み込み中にエラーが発生した場合のバッファの使い回しもできない。
この関数の性能は十分であり、大抵の場合は使いやすさと型安全性の兼ね合いとしては見合うものである。
とは言え性能をより細かく制御する必要がある場合には、間違いなく直接Read::read_to_string
を使うべきである。
ファイルを読み込む場合など特殊な状況において、この関数は読み込む入力のサイズに基づいてメモリを事前に確保することに注意されたい。
このような場合、手動で事前確保したバッファにてRead::read_to_string
を使用した場合と同じように性能が向上するはずである。
エラー
この関数は出力(String
)がResult
に内包されるため、エラー処理を強制される。
発生しうるエラーについてはRead::read_to_string
を参照されたい。
何かエラーが発生した場合はErr
を受け取るため、バッファが空だったり部分的に満たされたりする心配はない。
サンプル
fn main() -> io::Result<()> { let stdin = io::read_to_string(io::stdin())?; println!("標準入力:"); println!("{stdin}"); Ok(()) }
use std::io; fn main() -> io::Result<()> { let stdin = io::read_to_string(io::stdin())?; println!("標準入力:"); println!("{stdin}"); Ok(()) }
変更点リスト
言語
#[non_exhaustive]
の付いたバリアントを持つ列挙型でのas
をエラーにしたlet else
を安定化- 関連型でのジェネリクス(GATs)を安定化
- Clippy由来のリント
let_underscore_drop
・let_underscore_lock
を追加 - 任意のラベル付きブロックからの
break
("label-break-value")を安定化 - 未初期化の整数・浮動小数点数・生ポインタは即座に未定義動作と見做されるようになった。
未初期化のメモリを操作するには
MaybeUninit
を使うのが正しい方法である - Windowsのx86_64・aarch64・thumbv7aにおけるraw-dylibを安定化
- 外部のADT(※訳注:代数的データ型)向けDrop実装を許可しない
コンパイラ
- Linuxで-Csplit-debuginfoを安定化
- 複数のバリアントにデータがあってもニッチに詰める最適化を使用
- 関連型の射影が、内包する型を解決する前に正しい型であるかが検証されるようになった
- 略記でない可視性を正しく文字列化
- unsize時に構造体のフィールド型を正規化
- LLVM15に更新
- aarch64の呼び出し規約を必要時にゼロ拡張するよう修正
- デバッグ情報:列挙型をC++のようなエンコードに一般化
- リント
special_module_name
を追加 -C instrument-coverage
を使用した際、初期状態では固有のprofrawファイルを生成するようになった- iOS/tvOSターゲット向けに動的リンクを許可
新しいターゲット
- armv4t-none-eabiをTier 3のターゲットとして追加
- powerpc64-unknown-openbsdとriscv64-unknown-openbsdTier 3のターゲットとして追加
- Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照
ライブラリ
- derive(PartialEq)では
PartialEq::ne
を生成しない - Windowsの乱数発生器:初期状態で
BCRYPT_RNG_ALG_HANDLE
を使用 System
とシステムのメモリ操作関数呼び出しの混合を禁止- ブロックしない標準入出力・エラーへの書き込みはに対応していないことを文書化
std::layout::Layout
の大きさをalign
に繰り上げた際はisize::MAX
を超えてはならない。 これはLayout::from_size_align_unchecked
の安全のための必要条件も変える
安定化されたAPI
std::backtrace::Backtrace
Bound::as_ref
std::io::read_to_string
<*const T>::cast_mut
<*mut T>::cast_const
以下のAPIが定数文脈で使えるようになった。
Cargo
- ハッシュが部分的であってもGitHubの高速な経路を適用
- Cargoのバイナリ用パスが既にPATHに存在する場合、そのパスをPATHに追加しない
- 保留キュー内の優先順位を考慮。 これはCargoにおけるジョブのスケジューリングを少し最適化するもので、一般的にはクレートの系譜が大規模な場合に小さな改善が見られる
互換性メモ
std::layout::Layout
の大きさをalign
に繰り上げた際はisize::MAX
を超えてはならない。 これはLayout::from_size_align_unchecked
の安全のための必要条件も変えるPollFn
はクロージャがUnpin
である場合のみUnpin
を実装するようになった。 これは暗黙的なUnpinの実装に依存していた場合に発生する可能性のある破壊的な変更である。 この変更がなされた理由の詳細についてはPRの議論を参照されたい- std::char::EscapeAsciiからExactSizeIteratorの実装を削除。 これは標準ライブラリの外部から触れる領域に対する後方互換性のない変更であるが、実用性に影響する可能性はないと思われる
- 単一の繰り返し使用されたライフタイムが戻り値の型での省略に適格であると見做されないようになった。 この動作は1.64.0で意図せず変更されたものであり、今回のリリースではこの変更を再度エラーにするよう元に戻されている
- 無効化されていた初期段階の構文用gate(※訳注:有効化されていない実験的機能の使用を防ぐもの)が、将来的な非互換のリントとして再有効化された
※訳注:#[cfg]
などで省かれた部分では実験的構文への警告が出ていなかったのを出るようにしたということ - 最低限の外部LLVMを13に更新
- ファイル記述子を標準入出力の記述子に複製しない
- RLSの終焉
- クレートの種類を設定するための
#![cfg_attr(..., crate_type = ...)]
の使用を禁止。 これは前方互換性のリントdeprecated_cfg_attr_crate_type_nameを拒否に強める llvm-has-rust-patches
により、LLVMにRust特有のパッチがあるものとして扱うようビルドシステムを設定できる。 このオプションは、組み込みのLLVMではなくパッチを適用したLLVMを使ってllvm-config
経由でRustをビルドしているディストリビューションで必要な場合がある- 3言語以上(例:Objective CとC++、Rust)を1つのバイナリに結合すると、
lld
を使用するときにリンカの制限に当たることがある。詳細はIssue 102754(※訳注:英語ページ)を参照されたい
内部の変更
これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部では重要なパフォーマンス改善をもたらす。
- シェルスクリプト
x.sh
・x.ps1
を追加 - コンパイルテスト:ハードコーディングされたテーブルではなくtarget cfgを使用
- rlibからビットコードを読み込むのに、LLVMではなくobjectクレートを使用
※訳注:LLVM以外のコード生成バックエンドを見据えた変更っぽい - 最適化済みコンパイル向けにMIRのインライン化を有効化。 これにより実際のクレートをコンパイルする時間が3~10%ほど改善される。計測結果(※訳注:英語ページ)を参照のこと
関連リンク
さいごに
次のリリースのRust 1.66は12/16(金)にリリースされる予定です。
1.66では特定の値への最適化をできるだけ阻止するblack_box
関数や、パターンでの..=X
が使えるようになる予定です。