はじめに
2020年7月16日にRust 1.45.0がリリースされました。途中にパッチリリースの1.44.1が挟まりましたが、だいたい6週間ごとのリリースサイクルが守られているようです。この記事ではAnnouncing Rust 1.45.0の内容を基に1.45.0の新機能を確認してみたいと思います。
なお、Rustのインストールやアップデートをしてくれる Rustupも7月8日に1.22.1になっているので合わせてアップデートすると良いかと思います。以下でサクッと上がると思います。
$ rustup self update
そのうえで、
$ rustup update stable
すれば 1.45.0が使えるようになります。
新機能
型変換の問題の修正
これは新機能というより問題の修正ですが、以前のRustだとCastする際に値の範囲を越えると未定義の動作になる(つまりどうなるかわからない)という問題がありました。例えば、以下のような関数を考えます。これは32ビット浮動小数点型の値を8ビットの符号なし整数型に変換するものです。
pub fn cast(x: f32) -> u8 {
x as u8
}
出力が8ビットの符号なし整数型なので取りうる値は0から255までで、それをそれを越えると何がおこるかわからない これはRustのコンパイラバックエンドで使っている LLVMがそういうコードを出力するからですが、よろしくないですよね。この問題自体はRustが1.0になるだいぶ前の2013年からあって(7年前!)、でもここまで解決されずに残っていました。
上記の関数を以下のように使ってみます。300.0 は許容範囲の 255 を越えているのでxに何が入るかはわかりません。私の場合は x: 44が表示されましたが、人によって異なる値が表示されるはずです。
fn main() {
let f = 300.0;
let x = cast(f);
println!("x: {}", x);
}
コンパイル時にエラーにならず、実行時にもこれが起きていることがわからない。厄介ですね。こういうのがスルーされるのはRustっぽくないです。
で、このバグには「cast soundness」バグという名前が付けられていました。「型変換の不健全」バグという感じでしょうか。名前をつけられていつか直すものとして追いかけられていましたが、どう直すのが良いかが明確ではなかったので修正には時間がかかりました。
最終的に以下の結論に至り、今回の1.45.0で修正されました。
asは saturation cast (飽和型変換)を行う
型変換先の値の範囲を超える場合にはその最大値あるいは最小値に変換する
この動作をさせたくない時には unsafe型変換を使う
これは配列でarray[i]とした場合は配列がi+1個の要素を持っていることをチェックし、そのチェックを外したい時には unsafeキーワードを使うのと似ています。
1.45.0で以下のコードを実行してみると動作がわかると思います。
fn main() {
println!("{}", cast(300.0)); // --> 255
println!("{}", cast(-100.0)); // --> 0
println!("{}", cast(f32::NAN)); // --> 0
}
値が型変換先の範囲に入っていることがわかっているので、余計なチェックをさせたくない場合には unsafeをつかって以下のようにできます。
let x: f32 = 1.0;
let y: u8 = unsafe { x.to_int_unchecked() };
速度重視の場合にはありかも知れませんが、コンパイラがよろしくやってくれる場合もあり、あまりオススメではないようです。
関数的手続きマクロの利用可能場所が増える
まず今回の変更の前に手続きマクロについて簡単に書いておきます。
Rust 1.30.0で手続きマクロが導入されました。これは、TokenStream (Rustコードの断片)を入力としTokenStreamを出力とするマクロです。例えば、以下のような手続きマクロを考えてみます。
use proc_macro::TokenStream;
#[proc_macro]
pub fn make_func(item: TokenStream) -> TokenStream {
format!("fn func() -> u32 {{ {} * 2 }}", item)
.parse()
.unwrap()
}
これは func()という関数定義を返すマクロですが、itemで指定した式に*2した値を返す関数です。
使う側では
use func_proc_macros::make_func;
make_func!(1 + 2);
fn main() {
println!("{}", func());
}
といった形で使えます。これを実行すると 5が結果として出力されます。これを理解するのにマクロがどのように展開されているかを確認してみます。
$ cargo install cargo-expand
$ cargo expand
を実行すると、make_func!(1 + 2)が以下のように展開されていることがわかります。
fn func() -> u32 {
1 + 2 * 2
}
これを見ると確かに返り値は5になりますね。item * 2なので一瞬答えは 6かなと思うのですが、式を文字列で貼り付けているだけなので 1 + 2 * 2となり、掛け算が先に行われ答えは 5になります。
この機能は以前のバージョンから利用可能でしたが、使える場所が限られていました。上記のように、トップレベルの item position というところにしか置けませんでした。これが、Rust 1.45.0からは他のところにも置けるようになりました。具体的には以下の場所が新たに追加されました。
-
- expression position: 式が使われる場所
-
- pattern position: matchでパターンマッチングが行われる場所
- statement position: 文が使われる場所
fn main() {
let expr = mac!(); // expression position
match expr {
mac!() => {} // pattern position
}
mac!(); // statement position
}
これまでは item positionにしか置けなかったので関数定義などトップレベルに置けるものしかマクロ化できませんでしたが、これからは式や文なども手続きマクロを使って書くことができるようにになり応用範囲がぐっと広がるのではないかと思います。
なお、この機能を使った面白い実例がRustで+++を実現するに載せられています。ご興味のある方はぜひそちらもどうぞ。
charをレンジで使えるようになる
文字型をレンジで使えるようになりました。以下を実行するとabcdefghijklmnopqrstuvwxyzが出力されます。
for ch in 'a'..='z' {
print!("{}", ch);
}
println!();
まとめ
Rust 1.45.0の新機能をまとめてみました。Pythonは1年に一度のリリース(それでも短くなった)ですが、それに比べると6週間に一度というのは随分と短くその分変更点もコンパクトになりますね。これからも気になる新機能が出てきたら書いてみたいと思います。