あずんひの日

あずんひの色々を書き留めるブログ

Rust 1.52を早めに深掘り

あずんひ(@aznhe21)です。みなさんGWはどこ行きましたか?私はこれから考えます。

さて、いつもは会社のテックブログで投稿してる深掘りシリーズですが、 Rust 1.52がGW中にリリースされるということで(大晦日リリースのRust 1.49に引き続き)個人ブログからお送りします。

5/7はソニーの設立日 It's a Rust

ピックアップ

個人的に注目する変更点を「ピックアップ」としてまとめました。 全ての変更点を網羅したリストは変更点リストをご覧ください。

unsafe関数内でのみだりなunsafe処理を防げるようになった

これまではunsafe関数内では全てのunsafeな処理が許可されていました。 しかし、これでは関数自体はunsafeであっても処理の大半が安全である場合に、安全な処理との区別がしづらいという問題があります。

unsafe fn read_f32(x: &[u8]) -> Option<f32> {
    if x.len() < std::mem::size_of::<f32>() {
        return None;
    }

    // 危険なのはこの行だけ
    let r = std::ptr::read_unaligned(x as *const _ as *const f32);
    Some(r)
}

そこでunsafe関数の中でunsafeブロックを使って危険な処理をマークすることを思い付きますが、 もともとunsafe関数内では全てunsafeな処理として見做されているためコンパイラに警告されてしまいます。

unsafe fn read_f32(x: &[u8]) -> Option<f32> {
    if x.len() < std::mem::size_of::<f32>() {
        return None;
    }

    let r = unsafe { std::ptr::read_unaligned(x as *const _ as *const f32) };
    //      ^^^^^^ unnecessary `unsafe` block
    Some(r)
}

Rust 1.52ではunsafe_op_in_unsafe_fnというリントが追加され、unsafeな関数直下ではunsafeな処理を警告ないし禁止することが出来るようになりました。 これによりunsafe関数内でのunsafeブロックが意味を持つようになるのです。

#[deny(unsafe_op_in_unsafe_fn)] // 関数内での直接のunsafe処理を禁止する
unsafe fn read_f32(x: &[u8]) -> Option<f32> {
    if x.len() < std::mem::size_of::<f32>() {
        return None;
    }

    // SAFETY: xはf32の大きさ以上であり、読み取っても問題は無い
    let r = unsafe { std::ptr::read_unaligned(x as *const _ as *const f32) };
    Some(r)
}

安定化されたAPIのドキュメント

安定化されたAPIのドキュメントを独自に訳して紹介します。リストだけ見たい方は安定化されたAPIをご覧ください。

Arguments::as_str

原典

impl<'a> Arguments<'a> {
    #[stable(feature = "fmt_as_str", since = "1.52.0")]
    #[inline]
    pub fn as_str(&self) -> Option<&'static str>
    { /* 実装は省略 */ }
}

フォーマットすべき引数が無い場合、フォーマット済み文字列を取得する。

最も些細なケースで、メモリ確保を避けるために使うことが出来る。

サンプル

use std::fmt::Arguments;

fn write_str(_: &str) { /* ... */ }

fn write_fmt(args: &Arguments) {
    if let Some(s) = args.as_str() {
        write_str(s)
    } else {
        write_str(&args.to_string());
    }
}
assert_eq!(format_args!("hello").as_str(), Some("hello"));
assert_eq!(format_args!("").as_str(), Some(""));
assert_eq!(format_args!("{}", 1).as_str(), None);

char::MAX

原典

#[lang = "char"]
impl char {
    #[stable(feature = "assoc_char_consts", since = "1.52.0")]
    pub const MAX: char = '\u{10ffff}';
}

charが持つことの出来る、最大の有効なコードポイント。

charUnicodeのスカラ値つまりコードポイントであるが、ある範囲に限られる。 MAXは有効なUnicodeのスカラ値の中で最大の有効なコードポイントである。

char::REPLACEMENT_CHARACTER

原典

#[lang = "char"]
impl char {
    #[stable(feature = "assoc_char_consts", since = "1.52.0")]
    pub const REPLACEMENT_CHARACTER: char = '\u{FFFD}';
}

U+FFFD REPLACEMENT CHARACTER(�)はUnicodeでデコードエラーを表すのに使われる。

例えば不正なUTF-8バイト列をString::from_utf8_lossyに渡した場合に発生する。

char::UNICODE_VERSION

原典

#[lang = "char"]
impl char {
    #[stable(feature = "assoc_char_consts", since = "1.52.0")]
    pub const UNICODE_VERSION: (u8, u8, u8) = crate::unicode::UNICODE_VERSION;
}

charstrのメソッドでのUnicode部分が基づくUnicodeのバージョン。

Unicodeの新バージョンは定期的にリリースされるが、それに伴って標準ライブラリのUnicodeに依存する全てのメソッドも更新される。 従って、時間と共にcharstrのメソッドやこの定数の値は変更される。これは破壊的変更とは見做されない

バージョン番号の体型は Unicode 11.0 or later, Section 3.1 Versions of the Unicode Standard で説明されている。

char::decode_utf16

原典

#[lang = "char"]
impl char {
    #[stable(feature = "assoc_char_funcs", since = "1.52.0")]
    #[inline]
    pub fn decode_utf16<I: IntoIterator<Item = u16>>(iter: I) -> DecodeUtf16<I::IntoIter>
    { /* 実装は省略 */ }
}

iter内のUTF-16エンコードされたコードポイントのイテレータを生成する。 ペアになっていないサロゲートErrとして返す。

サンプル

基本的な用法:

use std::char::decode_utf16;

// 𝄞mus<不正>ic<不正>
let v = [
    0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834,
];

assert_eq!(
    decode_utf16(v.iter().cloned())
        .map(|r| r.map_err(|e| e.unpaired_surrogate()))
        .collect::<Vec<_>>(),
    vec![
        Ok('𝄞'),
        Ok('m'), Ok('u'), Ok('s'),
        Err(0xDD1E),
        Ok('i'), Ok('c'),
        Err(0xD834)
    ]
);

Errを置換文字に置き換えることで不可逆なデコーダーを得られる。

use std::char::{decode_utf16, REPLACEMENT_CHARACTER};

// 𝄞mus<不正>ic<不正>
let v = [
    0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834,
];

assert_eq!(
    decode_utf16(v.iter().cloned())
       .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
       .collect::<String>(),
    "𝄞mus�ic�"
);

char::from_digit

原典

#[lang = "char"]
impl char {
    #[stable(feature = "assoc_char_funcs", since = "1.52.0")]
    #[inline]
    pub fn from_digit(num: u32, radix: u32) -> Option<char>
    { /* 実装は省略 */ }
}

与えられた基数で数値をcharに変換する。

'radix'はしばしば'base'とも呼ばれる。いくつかの一般的な値において、基数2は2進数を、基数10は10進数を、基数16は16進数を表す。 また、任意の基数がサポートされる。

入力が指定された基数の数字でない場合、from_digit()Noneを返す。

パニック

基数が36より大きい場合はパニックする。

サンプル

基本的な用法:

use std::char;

let c = char::from_digit(4, 10);

assert_eq!(Some('4'), c);

// 10進数の11は、16進数では1桁となる
let c = char::from_digit(11, 16);

assert_eq!(Some('b'), c);

入力が数字でない場合はNoneを返す。

use std::char;

let c = char::from_digit(20, 10);

assert_eq!(None, c);

大きい基数を渡した場合パニックを引き起こす。

use std::char;

// パニックする
char::from_digit(1, 37);

char::from_u32_unchecked

原典

#[lang = "char"]
impl char {
    #[stable(feature = "assoc_char_funcs", since = "1.52.0")]
    #[inline]
    pub unsafe fn from_u32_unchecked(i: u32) -> char
    { /* 実装は省略 */ }
}

正当性を無視してu32charに変換する。

全てのcharu32として有効であり、キャストが出来ることに注意されたい。

let c = '💯';
let i = c as u32;

assert_eq!(128175, i);

しかし逆は言えず、u32の値が全てcharとして有効というわけではない。 from_u32_unchecked()はこれを無視し、不正な値を生成するかもしれないまま盲目的にcharにキャストする。

安全性

不正はcharの値を生成するかもしれないため、この関数はunsafeである。

この関数の安全なバージョンはfrom_u32関数を参照されたい。

サンプル

基本的な用法:

use std::char;

let c = unsafe { char::from_u32_unchecked(0x2764) };

assert_eq!('❤', c);

char::from_u32

原典

#[lang = "char"]
impl char {
    #[stable(feature = "assoc_char_funcs", since = "1.52.0")]
    #[inline]
    pub fn from_u32(i: u32) -> Option<char>
    { /* 実装は省略 */ }
}

charu32に変換する。

全てのcharu32として有効であり、キャストが出来ることに注意されたい。

let c = '💯';
let i = c as u32;

assert_eq!(128175, i);

しかし逆は言えず、u32の値が全てcharとして有効というわけではない。 入力がcharとして有効な値でない場合、from_u32()Noneを返す。

これらのチェックをしない、unsafeなバージョンの関数はfrom_u32_uncheckedを参照されたい。

サンプル

基本的な用法:

use std::char;

let c = char::from_u32(0x2764);

assert_eq!(Some('❤'), c);

入力が不適切なcharの場合、Noneを返す。

use std::char;

let c = char::from_u32(0x110000);

assert_eq!(None, c);

slice::partition_point

原典

#[lang = "slice"]
#[cfg(not(test))]
impl<T> [T] {
    #[stable(feature = "partition_point", since = "1.52.0")]
    pub fn partition_point<P>(&self, mut pred: P) -> usize
    where
        P: FnMut(&T) -> bool,
    { /* 実装は省略 */ }
}

与えられた述語関数において分割位置となるインデックスを返す(2つ目の区画での最初の要素のインデックス)。

スライスは与えられた述語関数で分割されていると仮定される。 つまり、述語関数がtrueを返す全ての要素はスライスの先頭に、述語関数がfalseを返す全ての要素はスライスの末尾に位置していると仮定される。 例えば[7, 15, 3, 5, 4, 12, 6]は述語関数x % 2 != 0で分割されている(全ての奇数は先頭に、全ての偶数は末尾にある)。

もしスライスが分割されていない場合、このメソッドは二分法の一種を使用するため、戻り値は不定で無意味な値となる。

binary_searchbinary_search_by、 またbinary_search_by_keyも参照されたい。

サンプル

let v = [1, 2, 3, 3, 5, 6, 7];
let i = v.partition_point(|&x| x < 5);

assert_eq!(i, 4);
assert!(v[..i].iter().all(|&x| x < 5));
assert!(v[i..].iter().all(|&x| !(x < 5)));

str::rsplit_once

原典

#[lang = "str"]
#[cfg(not(test))]
impl str {
    #[stable(feature = "str_split_once", since = "1.52.0")]
    #[inline]
    pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>
    where
        P: Pattern<'a, Searcher: ReverseSearcher<'a>>,
    { /* 実装は省略 */ }
}

文字列を指定された区切りで最後の出現位置で分割し、区切りの前部分と後部分を返す。

サンプル

assert_eq!("cfg".rsplit_once('='), None);
assert_eq!("cfg=foo".rsplit_once('='), Some(("cfg", "foo")));
assert_eq!("cfg=foo=bar".rsplit_once('='), Some(("cfg=foo", "bar")));

str::split_once

原典

#[lang = "str"]
#[cfg(not(test))]
impl str {
    #[stable(feature = "str_split_once", since = "1.52.0")]
    #[inline]
    pub fn split_once<'a, P: Pattern<'a>>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>
    { /* 実装は省略 */ }
}

文字列を指定された区切りで最初の出現位置で分割し、区切りの前部分と後部分を返す。

サンプル

assert_eq!("cfg".split_once('='), None);
assert_eq!("cfg=foo".split_once('='), Some(("cfg", "foo")));
assert_eq!("cfg=foo=bar".split_once('='), Some(("cfg", "foo=bar")));

変更点リスト

言語

コンパイラ

以下のターゲットにティア3※サポートを追加した。

※Rustの、ティアで分けられたプラットフォームの詳細はPlatform Supportのページ(英語)を参照

ライブラリ

安定化されたAPI

また、以前から安定化されていたAPIのうち以下のAPIが定数化された。

Rustdoc

- [x] Complete
- [ ] Todo

その他

内部の変更

これらの変更がユーザーに直接利益をもたらすわけではないものの、コンパイラ及び周辺ツール内部での重要なパフォーマンス改善をもたらす。

互換性メモ

関連リンク

さいごに

次のリリースのRust 1.53は6/17(金)にリリースされる予定です。1.53ではFoo(Bar(x) | Baz(x))のようなパターンが使えるようになるようです。

ライセンス表記

  • 冒頭の画像中にはRust公式サイトで配布されているロゴを使用しており、 このロゴはMozillaもしくはRust財団によってCC-BYの下で配布されています
  • 冒頭の画像はいらすとやさんの画像を使っています。いつもありがとうございます