【Rust】Unsafe Rust

1. 概要 

今回はunsafe Rustについて学んでいきたいと思います。

2. unsafe

Rustはメモリの安全性がコンパイルで保証されてきました。ですが、Rustにはメモリの安全性をプログラマ自身が保証することで、Rustが推奨していない機能を使うことができます。これを「unsafe Rust」と言います。動きは今までと変わらないものの、プログラマ自身が安全性を担保しなければならないと言うことでもあります。

unsafeブロックを使用することで次のようなことができます。

  • ミュータブルなグローバル変数へのアクセス
  • 生ポインターのデリファレンス
  • unsafe関数の利用
  • Rust以外の言語で書かれた、共有ライブラリ関数の利用

まずはミュータブルなグローバル変数へのunsafeブロックを使用しないでアクセスしてみましょう。

// グローバル変数を定義
static mut TAX: f32 = 0.1;

fn main() {
    // unsafe {
        // グローバル変数を変更する
        TAX = 0.08;
        println!("Price: {}", TAX * 300.0);
    // }
}

結果

   Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
error[E0133]: use of mutable static is unsafe and requires unsafe function or block
 --> src/main.rs:7:5
  |
7 |     TAX = 0.08;
  |     ^^^^^^^^^^ use of mutable static
  |
  = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior

error[E0133]: use of mutable static is unsafe and requires unsafe function or block
 --> src/main.rs:8:27
  |
8 |     println!("Price: {}", TAX * 300.0);
  |                           ^^^ use of mutable static
  |
  = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior

For more information about this error, try `rustc --explain E0133`.
error: could not compile `hello_rust` due to 2 previous errors

安全ではない変更可能なスタティクな変数へのアクセスはunsafe関数または、unsafeブロックが必要だと怒られました。

では、上記のプログラムのコメントアウトの部分を解除して実行してみましょう。

   Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
     Running `target/debug/hello_rust`
Price: 24

今度は成功しました。このように、Rust内で安全ではない操作をする時には、unsafeブロックを使用することで、操作が可能なります。

次に、生ポインタのデリファレンスについて確認していきましょう。本来、Rustの借用規則で、不変参照と可変参照は同じスコープ内で存在することはできません。

まず、生ポインタとは

  • 同じスコープ内で不変と可変なポインタや、複数の可変なポインタが存在することなど、借用規則を無視できる
  • 有効なメモリを指しているとは保証されない
  • nullの可能性がある
  • 自動クリーンアップを実装されていない

生ポインタを使用することで、保証された安全性を諦めてパフォーマンスを向上させたりすることができます。

では、実際にプログラムを見てみましょう。

fn main() {
    let mut num = 5;
    let r1 = #
    let r2 = &mut num;

    println!("{}", r1);
    println!("{}", r2);
}

結果

   Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
error[E0502]: cannot borrow `num` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:14
  |
3 |     let r1 = #
  |              ---- immutable borrow occurs here
4 |     let r2 = &mut num;
  |              ^^^^^^^^ mutable borrow occurs here
5 | 
6 |     println!("{}", r1);
  |                    -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `hello_rust` due to previous error

エラーになりました。生ポインタを使用してエラーを回避します。

fn main() {
    let mut num = 5;
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("{}", *r1);
        println!("{}", *r2);
    }
}

結果

   Compiling hello_rust v0.1.0 (/Users/ryoutaookawa/hello_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.49s
     Running `target/debug/hello_rust`
5
5

エラーにならず実行できました。生ポインタには2種類あって、「as *const」は読込みのみ、「as *mut」は読書きの両方が可能です。生ポインタは普通のRust内でも使用することができますが、生ポインタのデリファレンスはunsafeコード内でしかできません。あと、データ競合するかもしれないため注意が必要です。

次に、unsafe関数の利用について確認していきましょう。簡単な足し算をする関数を定義してみます。

fn main() {
    unsafe {
        // unsafe関数を呼び出す
        println!("{}", sum(10, 8));
    }
}

// unsafe関数を定義
unsafe fn sum(a: i32, b: i32) -> i32 {
    a + b
}

結果

   Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.37s
     Running `target/debug/hello_rust`
18

unsafe関数を定義するには、通常の関数にunsafeをつけるだけです。冒頭で書いた通り、unsafe関数は同じunsafe関数内か、unsafeブロック内での使用しかできません。

次に、unsafeコードの安全な抽象を行なっていきましょう。まずはエラーになるプログラムです。

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];
    let r = &mut v[..];
    let (a, b) = split_at_mut(r, 3);
    assert_eq!(a, &mut [1, 2, 3]);
    assert_eq!(b, &mut [4, 5, 6]);
}

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();

    assert!(mid <= len);

    (&mut slice[..mid], &mut slice[mid..])
}

結果

   Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
error[E0499]: cannot borrow `*slice` as mutable more than once at a time
  --> src/main.rs:14:30
   |
9  | fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
   |                        - let's call the lifetime of this reference `'1`
...
14 |     (&mut slice[..mid], &mut slice[mid..])
   |     -------------------------^^^^^--------
   |     |     |                  |
   |     |     |                  second mutable borrow occurs here
   |     |     first mutable borrow occurs here
   |     returning this value requires that `*slice` is borrowed for `'1`

For more information about this error, try `rustc --explain E0499`.
error: could not compile `hello_rust` due to previous error

原因は、スライスから2度借用していることです。コードを見ると別々のところを借用しているのですが、コンパイラにはそれが分かりません。エラーを解決するには次にように修正します。

use std::slice;

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];
    let r = &mut v[..];
    let (a, b) = split_at_mut(r, 3);
    assert_eq!(a, &mut [1, 2, 3]);
    assert_eq!(b, &mut [4, 5, 6]);
}

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

結果

   Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.51s
     Running `target/debug/hello_rust`

まず、「as_mut_ptr」メソッドを使用して、生ポイントにアクセスしています。この時はi32型の可変スライスなので、「as_mut_ptr」メソッドは型「*mut i32」の生ポインタを返します。

次に、「slice::from_raw_parts_mut」を使用します。この関数は、生ポインタと長さを取得し、スライスを生成します。

「split_at_mut」関数は、一部だけがunsafeコードなので、unsafeな関数にする必要がなく、普通の関数から呼び出しが可能です。このように、安全にunsafeコードを使用する関数を実装することで、unsafeコードへの安全な抽象化を行いました。

次に、Rust以外の言語で書かれた、ライブラリの使用について確認していきましょう。

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

結果

   Compiling hello_rust v0.1.0 (/Users/ryoutaookawa/hello_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 9.87s
     Running `target/debug/hello_rust`
Absolute value of -3 according to C: 3

Rust以外で書かれたライブラリを使用するのは、「extern」を使用します。externブロック内に書かれているコードは全て、unsafeになります。なぜかと言うと、他の言語では、Rustの規則や保証が強制されず、コンパイラもチェックできないので、 安全性を保証する責任はプログラマにあるからです。

3. まとめ

今回は、Unsafe Rustについてやっていきました。unsafeを利用することで、効率良くコードが書けたりする反面、未定義動作を引き起こしたりする場合があるので、過度な利用はやめたほうが良いなと感じました。ただ、使いこなせるようになれば、もっとRustを楽しめると思います。

投稿者プロフィール

OkawaRyouta
最新の投稿

関連記事

  1. 【Rust】構造体

  2. 【Rust】借用と参照、ライフタイム

  3. 【Rust】ベクタ、スライス

  4. 【Rust】タプル型、配列型

  5. 【Rust】変数、定数、シャドーイング

  6. 【Rust】文字型、論理値型

最近の記事

  1. Node.js
  2. AWS
  3. AWS
  4. flutter

制作実績一覧

  1. Checkeys