あずんひの日

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

Rust 1.70を早めに深掘り

本日6/2(金)にリリースされたRust 1.70の変更点を詳しく紹介します。 もしこの記事が参考になれば記事末尾から活動を支援頂けると嬉しいです。

ピックアップ

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

一度だけ初期化されるグローバル変数を書けるようになった

once_cellクレートにあったunsync::OnceCellsync::OnceCellが標準ライブラリに取り込まれ、 それぞれstd::cell::OnceCellstd::sync::OnceLockとして使えるようになりました。

これらは一度だけ初期化される変数のための型でOnceCellはスレッド安全ではないもの、OnceLockはスレッド安全なものであり、 とりわけ後者はグローバル変数として使えるため、定数文脈で扱えない値をグローバル化するのに便利です。 ただし使う度に初期化処理を書く必要があるのが玉に瑕です。

これまでもMutex<Option<T>>を使って似たようなことができましたが、 VAR.lock().unwrap().get_or_insert_with(|| Some(...))と長い上に値が使われる間ロックされ続けるというのが欠点でした。 OnceLockならVAR.get_or_init(|| ...)と短く、しかも戻り値は不変参照であるためロックがごく短い期間で済みます。

use std::sync::OnceLock;

static OS_NAME: OnceLock<String> = OnceLock::new();

fn main() {
    // `OS_NAME`はどちらかのスレッドで一度だけ初期化され、上書きされることはない
    let t1 = std::thread::spawn(|| println!("{}", OS_NAME.get_or_init(read_os_name)));
    let t2 = std::thread::spawn(|| some_process(OS_NAME.get_or_init(read_os_name)));
    t1.join().unwrap();
    t2.join().unwrap();
}

fn read_os_name() -> String {
    use std::io::BufRead;

    for line in std::io::BufReader::new(std::fs::File::open("/etc/os-release").unwrap()).lines() {
        let line = line.unwrap();
        if line.starts_with("NAME=\"") && line.ends_with('"') {
            return line[6..][..line.len() - 6 - 1].to_string();
        }
    }

    "unknown".to_string()
}

fn some_process(_os_name: &str) {
    // ...
}

Nightlyでは初期化用関数を合わせて保持して簡潔に書ける(lazy_staticクレートonce_cell::sync::Lazyに相当する)型である std::cell::LazyCellstd::sync::LazyLockがありますが、変性や関数の型を指定する方法に懸念があるため 安定化はまだ先のようです。

format_args!コンパイル時に展開されるようになった

format_args!やそれに依存するマクロ(write!println!log::info!など)で数値や文字列などがコンパイル時に展開されるようになりました。 これにより出力コードが最適化され、高速化やバイナリサイズの縮小などが期待されます。

fn main() {
    // 数値や文字列リテラルは展開され・・・
    println!("{} {}", 0, "hoge");
    // このように書くのとほぼ同じ意味となる
    println!("0 hoge");

    // ネストしたformat_args!も可能な限り展開され・・・
    println!("{}", format_args!("{} {}", "nargs", std::env::args().len()));
    // こちらと同じ意味となる
    println!("nargs {}", std::env::args().len());
}

またformat_args!の戻り値であるstd::fmt::Argumentsas_strメソッドにも影響があり、 これまでは引数が与えられていない場合のみ値が得られたところ、Rust 1.70からは展開される場合にも文字列リテラルを得られるようになります。

fn main() {
    // file!とline!はそれぞれ文字列と数値と解釈される結果展開可能なため、
    // Rust 1.70からは展開された文字列リテラルを得られる
    let s = format_args!("log@{}:{}", file!(), line!()).as_str();
    assert_eq!(s, Some("log@src/main.rs:4"));
}

OptionやResult内の値を使って判定処理を書けるようになった

Option::is_some_andにより、OptionSomeの時にその内部の値を使って判定をするといったことができるようになりました。 ResultでもResult::is_ok_andで値がOkのとき、Result::is_err_andで値がErrのときの判定ができます。

これまでもmap_ormatches!マクロなどを使っても同じ処理が書けましたが、こちらの方が読みやすいかもしれません。

fn main() {
    let x = Some("hoge".to_string());
    // 全部同じ意味
    assert!(x.as_ref().map_or(false, |v| v.starts_with("hoge")));
    assert!(matches!(&x, Some(v) if v.starts_with("hoge")));
    assert!(x.as_ref().is_some_and(|v| v.starts_with("hoge")));
}

Cargoでの高速なインデックス更新が既定で使用されるようになった

Rust 1.68で導入されたレジストリ用の疎(sparse)なプロトコルが既定で使用されるようになり、 何も設定しなくてもインデックス更新が高速化されるようになりました。

何らかの理由によりこのプロトコルを使用できない場合、環境変数CARGO_REGISTRIES_CRATES_IO_PROTOCOL=gitを設定するか、 ~/.cargo/config.tomlで以下のように設定することで古いプロトコルを使用することができます。

[registries.crates-io]
protocol = 'git'

最近のrust-analyzer

最近rust-analyzerに入った変更の中から、個人的に気になったものをピックアップしました。

安定化されていないAPIが候補に出なくなった

2023-04-17(v0.3.1481)で入った機能ですが、 バグのため使えるようになったのは2023-04-24(v0.3.1489)からです。

補完候補に出ていた安定化前のAPIが候補に出なくなりました。 使えない候補が出てくるのは地味にストレスだったので嬉しい変更です。

なお、rustupでNightlyを使う設定ではきちんと安定化前の候補も出てきます。

安定版Rust(左)とNightly版Rust(右)の補完候補
安定版Rust(左)とNightly版Rust(右)の補完候補

クロージャが何をキャプチャしているか詳細に分かるようになった

2023-05-01(v0.3.1498)2023-05-08(v0.3.1506)2023-05-15(v0.3.1514)2023-05-22(v0.3.1524)での変更です。

クロージャのかざし(hover)ヒントで、そのクロージャが変数をどのようにキャプチャしているかが分かるようになりました。 なおこのヒントは変数ではなくクロージャ自体のものであり、カーソルが|の上である必要があります。 またカーソルがmoveや引数を囲う|の上にある際、そのクロージャがキャプチャする変数がハイライトされるようになりました。

また既定では無効であるものの、VS Codeなど一部エディタでははめ込み(inlay)ヒントにも情報が表示されるようです。

クロージャのキャプチャする全変数が一目で分かる他かざしヒントでは変数ごとにムーブや可変参照といった種類が分かるため、 クロージャFnOnceにしたいのにFnMutになってしまう場合に原因が分かるなど便利です。

キャプチャしている各変数をハイライトさせたりキャプチャ種別を表示させたりした様子
キャプチャしている各変数をハイライトさせたりキャプチャ種別を表示させたりした様子

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

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

NonZero*::MIN

原典

impl NonZeroI8 {
    #[stable(feature = "nonzero_min_max", since = "1.70.0")]
    pub const MIN: Self = Self::new(i8::MIN).unwrap();
}

この非ゼロ整数型で表現できる最小の値で、i8::MINと同等。

注:整数型は原則としてMINからMAXまでの全整数を定めるが、符号付きの非ゼロ整数は特例であり、 0に「途切れ」がある。

サンプル

assert_eq!(NonZeroI8::MIN.get(), i8::MIN);
use std::num::NonZeroI8;
assert_eq!(NonZeroI8::MIN.get(), i8::MIN);

NonZero*::MAX

原典

impl NonZeroI8 {
    #[stable(feature = "nonzero_min_max", since = "1.70.0")]
    pub const MAX: Self = Self::new(i8::MAX).unwrap();
}

この非ゼロ整数型で表現できる最大の値で、i8::MAXと同等。

注:整数型は原則としてMINからMAXまでの全整数を定めるが、符号付きの非ゼロ整数は特例であり、 0に「途切れ」がある。

サンプル

assert_eq!(NonZeroI8::MAX.get(), i8::MAX);
use std::num::NonZeroI8;
assert_eq!(NonZeroI8::MAX.get(), i8::MAX);

BinaryHeap::retain

原典

impl<T: Ord> BinaryHeap<T> {
    #[stable(feature = "binary_heap_retain", since = "1.70.0")]
    pub fn retain<F>(&mut self, mut f: F)
    where
        F: FnMut(&T) -> bool,
    { /* 実装は省略 */ }
}

述語で指定された要素のみを保持する。

つまり、要素eにおいてf(&e)falseを返すすべての要素を削除する。 要素はソートされていない(かつ不特定の)順序で渡される。

サンプル

use std::collections::BinaryHeap;

let mut heap = BinaryHeap::from([-10, -5, 1, 2, 4, 13]);

heap.retain(|x| x % 2 == 0); // 偶数だけ保持

assert_eq!(heap.into_sorted_vec(), [-10, 2, 4])

Rc::into_inner

原典

impl<T> Rc<T> {
    #[inline]
    #[stable(feature = "rc_into_inner", since = "1.70.0")]
    pub fn into_inner(this: Self) -> Option<T>
    { /* 実装は省略 */ }
}

Rcが強参照を1つだけ持つ場合に内部の値を返す。

さもなくばNoneが返されRcはドロップする。

このRcを指す弱参照があったとしてもこのメソッドは成功する。

このRcの各複製に対して(※訳注:同時に)Rc::into_innerが呼ばれたとしても、どれか1つの呼び出しだけが内部の値を返すことが保証される。 つまるところ内部の値がドロップすることはない。

これはRc::try_unwrap(this).ok()と同等である(ただしRcには適用されない競合状態のためArcにおいては同等ではない)。

Arc::into_inner

原典

impl<T> Arc<T> {
    #[inline]
    #[stable(feature = "arc_into_inner", since = "1.70.0")]
    pub fn into_inner(this: Self) -> Option<T>
    { /* 実装は省略 */ }
}

Arcが強参照を1つだけ持つ場合に内部の値を返す。

さもなくばNoneが返されArcはドロップする。

このArcを指す弱参照があったとしてもこのメソッドは成功する。

このArcの各複製に対して(※訳注:同時に)Arc::into_innerが呼ばれたとしても、 どれか1つの呼び出しだけが内部の値を返すことが保証される。 つまるところ内部の値がドロップすることはない。

同様の式であるArc::try_unwrap(this).ok()にこの保証はない。 下記サンプルの最後やArc::try_unwrapの文書も参照のこと。

サンプル

Arc::into_innerがもたらす保証についての最小サンプル。

use std::sync::Arc;

let x = Arc::new(3);
let y = Arc::clone(&x);

// `Arc`の両複製に対して2つのスレッドが`Arc::into_inner`を呼び出す
let x_thread = std::thread::spawn(|| Arc::into_inner(x));
let y_thread = std::thread::spawn(|| Arc::into_inner(y));

let x_inner_value = x_thread.join().unwrap();
let y_inner_value = y_thread.join().unwrap();

// どちらかのスレッドが内部の値を受け取ることが保証される
assert!(matches!(
    (x_inner_value, y_inner_value),
    (None, Some(3)) | (Some(3), None)
));
// 各スレッドで代わりに`Arc::try_unwrap(x).ok()`や`Arc::try_unwrap(y).ok()`を
// 呼び出した場合、結果が`(None, None)`となる可能性がある

Arc::into_innerの必要性を示すより実践的なサンプル。

use std::sync::Arc;

// `Arc`を使った単純かつ単一なリンクリストの定義
#[derive(Clone)]
struct LinkedList<T>(Option<Arc<Node<T>>>);
struct Node<T>(T, Option<Arc<Node<T>>>);

// `Arc`のデストラクタに依存する長大な`LinkedList<T>`をドロップすると
// スタックオーバーフローが発生する可能性がある。これを防ぐため、
// ループで破棄を行う`Drop`を実装する
impl<T> Drop for LinkedList<T> {
    fn drop(&mut self) {
        let mut link = self.0.take();
        while let Some(arc_node) = link.take() {
            if let Some(Node(_value, next)) = Arc::into_inner(arc_node) {
                link = next;
            }
        }
    }
}

// `new`や`push`の実装は省略
impl<T> LinkedList<T> {
    /* ... */
}

// 上記`Drop`実装が`Arc::into_inner(arc)`の代わりに`Arc::try_unwrap(arc).ok()`を
// 使用していた場合、次のコードではその`Drop`実装でスタックオーバーフローを発生させる可能性がある

// 長大なリストを生成して複製
let mut x = LinkedList::new();
for i in 0..100000 {
    x.push(i); // xの先頭にiを加える
}
let y = x.clone();

// 並列に複製をドロップする
let x_thread = std::thread::spawn(|| drop(x));
let y_thread = std::thread::spawn(|| drop(y));
x_thread.join().unwrap();
y_thread.join().unwrap();
use std::sync::Arc;

// `Arc`を使った単純かつ単一なリンクリストの定義
#[derive(Clone)]
struct LinkedList<T>(Option<Arc<Node<T>>>);
struct Node<T>(T, Option<Arc<Node<T>>>);

// `Arc`のデストラクタに依存する長大な`LinkedList<T>`をドロップすると
// スタックオーバーフローが発生する可能性がある。これを防ぐため、
// ループで破棄を行う`Drop`を実装する
impl<T> Drop for LinkedList<T> {
    fn drop(&mut self) {
        let mut link = self.0.take();
        while let Some(arc_node) = link.take() {
            if let Some(Node(_value, next)) = Arc::into_inner(arc_node) {
                link = next;
            }
        }
    }
}

// `new`や`push`の実装は省略
impl<T> LinkedList<T> {
    /* ... */
    fn new() -> Self {
        LinkedList(None)
    }
    fn push(&mut self, x: T) {
        self.0 = Some(Arc::new(Node(x, self.0.take())));
    }
}

// 上記`Drop`実装が`Arc::into_inner(arc)`の代わりに`Arc::try_unwrap(arc).ok()`を
// 使用していた場合、次のコードではその`Drop`実装でスタックオーバーフローを発生させる可能性がある

// 長大なリストを生成して複製
let mut x = LinkedList::new();
for i in 0..100000 {
    x.push(i); // xの先頭にiを加える
}
let y = x.clone();

// 並列に複製をドロップする
let x_thread = std::thread::spawn(|| drop(x));
let y_thread = std::thread::spawn(|| drop(y));
x_thread.join().unwrap();
y_thread.join().unwrap();

std::cell::OnceCell

原典

#[stable(feature = "once_cell", since = "1.70.0")]
pub struct OnceCell<T>
{ /* フィールドは省略 */ }

一度だけ書き込むことができるCell。

Cellとは異なり)コピーや置換することなく、また(RefCellとは異なり)実行時の借用検査もなく、 内部値への共有の&Tな参照を得ることができる。ただしCell自体への可変参照がない場合は不変参照だけを得ることができる。

スレッドセーフ版はstd::sync::OnceLockを参照。

サンプル

use std::cell::OnceCell;

let cell = OnceCell::new();
assert!(cell.get().is_none());

let value: &String = cell.get_or_init(|| {
    "Hello, World!".to_string()
});
assert_eq!(value, "Hello, World!");
assert!(cell.get().is_some());

Option::is_some_and

原典

impl<T> Option<T> {
    #[must_use]
    #[inline]
    #[stable(feature = "is_some_and", since = "1.70.0")]
    pub fn is_some_and(self, f: impl FnOnce(T) -> bool) -> bool
    { /* 実装は省略 */ }
}

このOptionSomeであり、かつ内部の値が述語に適合する場合にtrueを返す。

サンプル

let x: Option<u32> = Some(2);
assert_eq!(x.is_some_and(|x| x > 1), true);

let x: Option<u32> = Some(0);
assert_eq!(x.is_some_and(|x| x > 1), false);

let x: Option<u32> = None;
assert_eq!(x.is_some_and(|x| x > 1), false);

NonNull::slice_from_raw_parts

原典

impl<T> NonNull<[T]> {
    #[stable(feature = "nonnull_slice_from_raw_parts", since = "1.70.0")]
    #[rustc_const_unstable(feature = "const_slice_from_raw_parts_mut", issue = "67456")]
    #[must_use]
    #[inline]
    pub const fn slice_from_raw_parts(data: NonNull<T>, len: usize) -> Self
    { /* 実装は省略 */ }
}

薄いポインタ(thin pointer)と長さから非NULLの生スライスを生成する。

引数len要素数でありバイト数ではない。

この関数は安全だが、戻り値を逆参照するのは不安全である。 安全性の要件についてはslice::from_raw_partsの文書を参照。

サンプル

use std::ptr::NonNull;

// 最初の要素へのポインタから始まるスライスポインタを生成する
let mut x = [5, 6, 7];
let nonnull_pointer = NonNull::new(x.as_mut_ptr()).unwrap();
let slice = NonNull::slice_from_raw_parts(nonnull_pointer, 3);
assert_eq!(unsafe { slice.as_ref()[2] }, 7);

(注:このサンプルはメソッドの使用法を人為的に示しているに過ぎず、 このような場合ではlet slice = NonNull::from(&x[..]);といったコードを書く方がより良い)

Result::is_ok_and

原典

impl<T, E> Result<T, E> {
    #[must_use]
    #[inline]
    #[stable(feature = "is_some_and", since = "1.70.0")]
    pub fn is_ok_and(self, f: impl FnOnce(T) -> bool) -> bool
    { /* 実装は省略 */ }
}

このResultOkであり、かつ内部の値が述語に適合する場合にtrueを返す。

サンプル

let x: Result<u32, &str> = Ok(2);
assert_eq!(x.is_ok_and(|x| x > 1), true);

let x: Result<u32, &str> = Ok(0);
assert_eq!(x.is_ok_and(|x| x > 1), false);

let x: Result<u32, &str> = Err("おはよー");
assert_eq!(x.is_ok_and(|x| x > 1), false);

Result::is_err_and

原典

impl<T, E> Result<T, E> {
    #[must_use]
    #[inline]
    #[stable(feature = "is_some_and", since = "1.70.0")]
    pub fn is_err_and(self, f: impl FnOnce(E) -> bool) -> bool
    { /* 実装は省略 */ }
}

このResultErrであり、かつ内部の値が述語に適合する場合にtrueを返す。

サンプル

use std::io::{Error, ErrorKind};

let x: Result<u32, Error> = Err(Error::new(ErrorKind::NotFound, "!"));
assert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), true);

let x: Result<u32, Error> = Err(Error::new(ErrorKind::PermissionDenied, "!"));
assert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), false);

let x: Result<u32, Error> = Ok(123);
assert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), false);

std::sync::atomic::Atomic*::as_ptr

原典

impl AtomicU8 {
    #[inline]
    #[stable(feature = "atomic_as_ptr", since = "1.70.0")]
    #[rustc_const_stable(feature = "atomic_as_ptr", since = "1.70.0")]
    pub const fn as_ptr(&self) -> *mut u8
    { /* 実装は省略 */ }
}

内包する整数への可変ポインタを返す。

戻り値の整数に対して非原子的な読み書き操作を行った場合、データ競合を引き起こす可能性がある。 このメソッドは、&AtomicU8ではなく*mut u8を受け取る関数など、専らFFIにおいて便利である。

この原子型への共有参照から*mutなポインタを返すことは、原子型は内部可変性で動作することから安全である。 原子型でのすべての修正は共有参照を通してその値を変更するものの、これは原子的な操作を使う限りは安全である。 返された生ポインタの使用にはunsafeブロックが要求されるが、 同時に「生ポインタへの操作は原子的でなければならない」という制約にも従う必要がある。

サンプル

use std::sync::atomic::AtomicU8;

extern "C" {
    fn my_atomic_op(arg: *mut u8);
}

let atomic = AtomicU8::new(1);

// SAFETY: `my_atomic_op`は原子的である限り安全
unsafe {
    my_atomic_op(atomic.as_ptr());
}

std::io::IsTerminal

原典

#[stable(feature = "is_terminal", since = "1.70.0")]
pub trait IsTerminal: crate::sealed::Sealed {
    #[stable(feature = "is_terminal", since = "1.70.0")]
    fn is_terminal(&self) -> bool;
}

記述子・ハンドルが端末・TTYを参照しているかを判断するためのトレイト。

必須のメソッド

fn is_terminal(&self) -> bool;

記述子・ハンドルが端末・TTYを参照している場合にtrueを返す。

Rustが端末の検出方法を知らないプラットフォームではこのメソッドはfalseを返す。また、 不正なファイル記述子を与えた場合など予期しないエラーが発生した場合にもfalseを返す。

プラットフォーム固有の動作

Windowsではコンソールを検出するのに加え、古いmsys・cygwinmingwなど疑似端末を検出するために デバイス名を元にしたヒューリスティックな手法(msys-cygwin-で始まり-ptyで終わるデバイス名は端末と見做す)が 使用される。これは将来的に変更される可能性があることに注意されたい。

std::os::linux::net::SocketAddrExt

原典

#[stable(feature = "unix_socket_abstract", since = "1.70.0")]
pub trait SocketAddrExt: Sealed {
    #[stable(feature = "unix_socket_abstract", since = "1.70.0")]
    fn from_abstract_name<N>(name: N) -> crate::io::Result<SocketAddr>
    where
        N: AsRef<[u8]>;

    #[stable(feature = "unix_socket_abstract", since = "1.70.0")]
    fn as_abstract_name(&self) -> Option<&[u8]>;
}
Linuxかつ(LinuxまたはAndroidでのみ使用可能。

SocketAddrへのプラットフォーム固有の拡張。

必須のメソッド

fn from_abstract_name<N>(name: N) -> crate::io::Result<SocketAddr>
where
    N: AsRef<[u8]>;

抽象名前空間UNIXソケットアドレスを生成する。

抽象名前空間ファイルシステムに項目を作成することなくUNIXソケットをバインドする Linux固有の拡張である。抽象ソケットはファイルシステムのレイアウトやパーミッションに 影響されず、ソケットを閉じる際に消去する必要もない。

抽象ソケットアドレス名にはゼロを含む任意のバイトを含められる。

エラー

名前がSUN_LEN - 1より長い場合はエラーが返される。

サンプル
use std::os::unix::net::{UnixListener, SocketAddr};
use std::os::linux::net::SocketAddrExt;

fn main() -> std::io::Result<()> {
    let addr = SocketAddr::from_abstract_name("秘密".as_bytes())?;
    let listener = match UnixListener::bind_addr(&addr) {
        Ok(sock) => sock,
        Err(err) => {
            println!("バインドできない:{err:?}");
            return Err(err);
        }
    };
    Ok(())
}
fn as_abstract_name(&self) -> Option<&[u8]>;

このアドレスが抽象名前空間にある場合、その内容を返す。

サンプル
use std::os::unix::net::{UnixListener, SocketAddr};
use std::os::linux::net::SocketAddrExt;

fn main() -> std::io::Result<()> {
    let name = "秘密".as_bytes();
    let name_addr = SocketAddr::from_abstract_name(name)?;
    let socket = UnixListener::bind_addr(&name_addr)?;
    let local_addr = socket.local_addr().expect("ローカルアドレスを取得できない");
    assert_eq!(local_addr.as_abstract_name(), Some(&name[..]));
    Ok(())
}

std::os::unix::net::UnixDatagram::bind_addr

原典

impl UnixDatagram {
    #[stable(feature = "unix_socket_abstract", since = "1.70.0")]
    pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result<UnixDatagram>
    { /* 実装は省略 */ }
}

指定されたパスにUNIXデータグラムソケットを生成する。

サンプル

use std::os::unix::net::{UnixDatagram};

fn main() -> std::io::Result<()> {
    let sock1 = UnixDatagram::bind("path/to/socket")?;
    let addr = sock1.local_addr()?;

    let sock2 = match UnixDatagram::bind_addr(&addr) {
        Ok(sock) => sock,
        Err(err) => {
            println!("バインドできない:{err:?}");
            return Err(err);
        }
    };
    Ok(())
}

std::os::unix::net::UnixDatagram::connect_addr

原典

impl UnixDatagram {
    #[stable(feature = "unix_socket_abstract", since = "1.70.0")]
    pub fn connect_addr(&self, socket_addr: &SocketAddr) -> io::Result<()>
    { /* 実装は省略 */ }
}

ソケットをアドレスに接続する。

サンプル

use std::os::unix::net::{UnixDatagram};

fn main() -> std::io::Result<()> {
    let bound = UnixDatagram::bind("/path/to/socket")?;
    let addr = bound.local_addr()?;

    let sock = UnixDatagram::unbound()?;
    match sock.connect_addr(&addr) {
        Ok(sock) => sock,
        Err(e) => {
            println!("接続できない:{e:?}");
            return Err(e)
        }
    };
    Ok(())
}

std::os::unix::net::UnixDatagram::send_to_addr

原典

impl UnixDatagram {
    #[stable(feature = "unix_socket_abstract", since = "1.70.0")]
    pub fn send_to_addr(&self, buf: &[u8], socket_addr: &SocketAddr) -> io::Result<usize>
    { /* 実装は省略 */ }
}

指定されたSocketAddrのソケットにデータを送信する。

成功時、書き込まれたバイト数を返す。

サンプル

use std::os::unix::net::{UnixDatagram};

fn main() -> std::io::Result<()> {
    let bound = UnixDatagram::bind("/path/to/socket")?;
    let addr = bound.local_addr()?;

    let sock = UnixDatagram::unbound()?;
    sock.send_to_addr("目玉焼き".as_bytes(), &addr).expect("send_to_addr関数が失敗");
    Ok(())
}

std::os::unix::net::UnixListener::bind_addr

原典

impl UnixListener {
    #[stable(feature = "unix_socket_abstract", since = "1.70.0")]
    pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result<UnixListener>
    { /* 実装は省略 */ }
}

指定されたソケットアドレスにバインドされるUnixListenerを生成する。

サンプル

use std::os::unix::net::{UnixListener};

fn main() -> std::io::Result<()> {
    let listener1 = UnixListener::bind("path/to/socket")?;
    let addr = listener1.local_addr()?;

    let listener2 = match UnixListener::bind_addr(&addr) {
        Ok(sock) => sock,
        Err(err) => {
            println!("バインドできない:{err:?}");
            return Err(err);
        }
    };
    Ok(())
}

std::path::Path::as_mut_os_str

原典

impl Path {
    #[stable(feature = "path_as_mut_os_str", since = "1.70.0")]
    #[must_use]
    #[inline]
    pub fn as_mut_os_str(&mut self) -> &mut OsStr
    { /* 実装は省略 */ }
}

内包するOsStrスライスへの可変参照をもたらす。

サンプル

use std::path::{Path, PathBuf};

let mut path = PathBuf::from("Foo.TXT");

assert_ne!(path, Path::new("foo.txt"));

path.as_mut_os_str().make_ascii_lowercase();
assert_eq!(path, Path::new("foo.txt"));

std::sync::OnceLock

原典

#[stable(feature = "once_cell", since = "1.70.0")]
pub struct OnceLock<T>
{ /* フィールドは省略 */ }

一度だけ書き込むことができる同期プリミティブ。

これはスレッドセーフなOnceCellであり、静的変数として使用できる。

サンプル

use std::sync::OnceLock;

static CELL: OnceLock<String> = OnceLock::new();
assert!(CELL.get().is_none());

std::thread::spawn(|| {
    let value: &String = CELL.get_or_init(|| {
        "Hello, World!".to_string()
    });
    assert_eq!(value, "Hello, World!");
}).join().unwrap();

let value: Option<&String> = CELL.get();
assert!(value.is_some());
assert_eq!(value.unwrap().as_str(), "Hello, World!");

変更点リスト

言語

コンパイラ

Rustのティア付けされたプラットフォーム対応の詳細はPlatform Supportのページ(※訳注:英語)を参照

ライブラリ

安定化されたAPI

Cargo

その他

互換性メモ

内部の変更

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

関連リンク

さいごに

次のリリースのRust 1.71は7/14(金)にリリースされる予定です。 Rust 1.71ではタプルと配列を相互に変換できるようになるようです。

ライセンス表記

  • この記事はApache 2/MITのデュアルライセンスで公開されている公式リリースノート及びドキュメントから翻訳・追記をしています
  • 冒頭の画像中にはRust公式サイトで配布されているロゴを使用しており、 このロゴはRust財団によってCC-BYの下で配布されています
  • 冒頭の画像中には標準C++財団のサイトで配布されているロゴを使用しており、このロゴは標準C++財団によって特定の条件下で配布されています
  • 冒頭の画像はいらすとやさんの画像を使っています。いつもありがとうございます