Open5

Rust メモ

kei3devkei3dev

conststatic の違い

conststatic での変数定義は、Rustにおいて似てはいますが、いくつかの重要な違いがあります。

const での定義

  • 不変: const で定義された値は不変です。つまり、その値を変更することはできません。
  • インライン: const で定義された値は、それを使用するたびに、コンパイル時にその場所にインライン展開されます。これは、メモリアドレスを持たないということを意味します。
  • ライフタイム: const で定義された値は、暗黙的に 'static ライフタイムを持ちます。これは、プログラムの実行期間全体にわたって存在することを意味します。

例:

const PI: f64 = 3.14159265358979323846;

static での定義

  • 不変または可変: static キーワードを使用すると、変数は不変(static)または可変(static mut)にすることができます。
  • メモリアドレス: static 変数はプログラムのメモリ空間に固定の場所を持ちます。これは、実際のメモリアドレスを持つことを意味します。
  • ライフタイム: static 変数も 'static ライフタイムを持ちます。つまり、これらもプログラムの実行期間全体にわたって存在します。

例:

static PI: f64 = 3.14159265358979323846;

比較と違い

  • const はコンパイル時にインライン展開されるため、メモリ使用量が少ないかもしれませんが、static はプログラムのメモリ空間に実際の場所を持ちます。
  • static 変数は、アドレスが必要な場合(例えば、安全でないコードでの使用や、特定のメモリアドレスにデータを配置する場合)に使われることがあります。
  • static mut を使用すると、変数を可変にすることができますが、これは安全でない操作を必要とするため、慎重に使用する必要があります。

したがって、conststatic は機能的に似ている面がありますが、使用方法や特定のユースケースに応じて選択する必要があります。基本的には、不変の値であれば const を、メモリアドレスが必要な場合や可変性が必要な場合には static(または static mut)を使用します。

kei3devkei3dev

Resultanyhow::Result の違い

Result型とanyhow::Resultは、Rustのエラー処理に関連するものだが、それぞれ異なる目的と特性を持っている。

  1. 標準のResult型:

    • 定義: Rustの標準ライブラリに含まれるResult型は、Result<T, E>として定義される。ここで、Tは成功時の型、Eはエラー時の型を表す。
    • 用途: 明示的にエラーの型を指定したい場合や、特定のエラー型として定義されたenumやstructを利用したい場合に使用される。
    • カスタムエラー: Result型を使用する場合、エラーの種類や情報を細かく制御できる。独自のエラータイプを定義して、エラーの理由や追加情報を詳細に示すことができる。
  2. anyhow::Result型:

    • 定義: anyhow::Result<T>は、anyhowクレートによって提供される型で、Result<T, anyhow::Error>として実際に定義される。このanyhow::Errorは、さまざまなエラーのソースからのエラーをキャッチして格納するための型。
    • 用途: 主にアプリケーションや実行可能なバイナリで使用されることを目的とする。エラーを詳細に分類することをあまり気にしない場面や、さまざまなエラーソースからのエラーを同じ型として扱いたい場合に便利。
    • シンプルなエラーハンドリング: anyhow::Resultは、エラーハンドリングをシンプルにすることを目的としている。これにより、さまざまなエラータイプを一つのanyhow::Errorタイプで統一的に扱うことができる。

まとめ:

  • 標準のResult型は、エラーの型を明示的に指定し、エラーハンドリングの詳細を制御する場面で利用される。
  • anyhow::Resultは、さまざまなエラーを一つの型で扱い、エラーハンドリングを簡略化することを目的とする。主にアプリケーションのトップレベルやバイナリレベルでのエラーハンドリングに適している。
kei3devkei3dev

CellRefCell の違い

CellRefCellはどちらも内部可変性を実現する方法ですが、大きな違いはスレッド安全性の有無です。

1. Cell

  • スレッド不安全
  • コピー可能
  • ライフタイムのチェックが不要
use std::cell::Cell;

fn main() {
    let c = Cell::new(5);

    let value = c.get();
    println!("初期値: {}", value); // 出力: 初期値: 5

    c.set(10);
    let new_value = c.get();
    println!("新しい値: {}", new_value); // 出力: 新しい値: 10
}

この例では、Cellを使って値を変更しています。Cellはスレッド不安全なため、複数のスレッドから同時にアクセスすると問題が発生する可能性があります。

2. RefCell

  • スレッドセーフではない(スレッド不安全)
  • コピー不可能
  • ライフタイムのチェックが必要
use std::cell::RefCell;

struct Data {
    value: RefCell<i32>,
}

fn main() {
    let data = Data { value: RefCell::new(5) };

    let mut value = data.value.borrow_mut();
    *value = 10;
    println!("新しい値: {}", *value); // 出力: 新しい値: 10
}

RefCellを使う場合は、borrow_mutメソッドで可変の参照を取得し、その値を変更する必要があります。RefCellもスレッドセーフではありませんが、ライフタイムのチェックが行われるため、コンパイル時に一部のエラーを検出できます。

RefCellはスレッド不安全ですが、スレッドセーフでない状況でも安全性を高めることができます。一方で、Cellはスレッド不安全かつコピー可能なため、単純な用途に適しています。

まとめ

  • Cellはスレッド不安全だがコピー可能で、ライフタイムチェックが不要
  • RefCellはスレッド不安全だがコピー不可能で、ライフタイムチェックが必要
  • どちらも内部可変性を実現するが、安全性とコピー可能性のトレードオフがある
  • 状況に応じて適切なものを選択する必要がある

参考記事

https://zenn.dev/tan_y/articles/307533011ac2c0

kei3devkei3dev

Send と Sync

  • Send:オブジェクトの所有権を別のスレッドへ移動することを許可しますが、その移動先のスレッドからはデータを変更することはできません。
  • Sync:オブジェクトへの不変参照を複数のスレッドから共有することを許可しますが、それらのスレッドからデータを変更することはできません。
  • 両方のトレイトとも、データの変更が許可されないことで、データ競合を回避しています。
項目 Send トレイト Sync トレイト
目的 オブジェクトの所有権を別のスレッドへ移動できる オブジェクトへの不変参照を複数のスレッドから共有できる
データの変更 許可されない 許可されない
スレッド間のデータ共有 所有権の移動のみ 不変参照の共有のみ
  • すべてのプリミティブ型はSendかつSyncである。
  • すべてのフィールドがSendかつSyncの構造体は、自動的にSendかつSyncの構造体となる。