やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustの高度な機能(Unsafe Rust)

 メモリ非安全コードを書く。

成果物

参考

Unsafe Rust

 Rustの内部にはメモリ非安全な言語がある。それが「unsafe Rust」である。

用途

 OSなど低レベルプログラミングをするとき。

unsafe super power

 unsafe RustはRustにはできない以下の4つができる。これを「unsafe superpower」と呼ぶ。

  • 生ポインタを参照外しする
  • unsafeな関数やメソッドを呼ぶ
  • 可変で静的な変数にアクセスしたり変更する
  • unsafeなトレイトを実装する

記法

unsafe {
    // unsafe Rustのコードをここに書く
}

生ポインタ

  • *const T: 不変な生ポインタ
  • *mut T: 可変な生ポインタ

 生ポインタを表す型は上記2種類である。このときの*は参照外しではない。unsafe Rust文脈内では生ポインタを意味する。

参照やスマートポインタとの違い

  • 借用規則を無視できる(同じ場所への不変と可変なポインタや複数の可変なポインタが存在することで)
  • 有効なメモリを指しているとは保証されない
  • nullの可能性がある
  • 自動的な片付けは実装されていない

from 有効な変数

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

 unsafeブロック外でも宣言できる。参照外しができないだけ。

 asを使ってキャストしている。

from 指定アドレス

fn main() {
    let address = 0x012345usize;
    let r = address as *const i32;
}

 指定したアドレスのポインタを得ることもできる。ただし、これをする利点はない。

参照外し

fn main() {
    let mut num = 5;
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;
    unsafe {
        println!("r1 is: {}", *r1);
        println!("r2 is: {}", *r2);
    }
}
$ rustc main.rs
$ ./main
r1 is: 5
r2 is: 5

 Rustの借用規則なら、不変参照と可変参照は同一スコープ内で存在できない。だが、生ポインタなら可能。そのせいでデータ競合するかもしれないため注意が必要。

unsafeな関数やメソッドを呼ぶ

unsafe fn dangerous() {}
fn main() {
    unsafe {
        dangerous();
    }
}

 もしunsafe {}ブロック内でdangerous関数を呼ばなければ以下エラー。

$ rustc main.rs
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
 --> main.rs:7:5
  |
7 |     dangerous();
  |     ^^^^^^^^^^^ call to unsafe function
  |
  = note: consult the function's documentation for information on how to avoid undefined behavior

unsafeコードに安全な抽象を行う

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

 split_at_mut関数はsafe Rustではコンパイルできない。2箇所で可変借用しているから。

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    assert!(mid <= len);
    (&mut slice[..mid], // error[E0499]: cannot borrow `*slice` as mutable more than once at a time
     &mut slice[mid..])
}
error[E0499]: cannot borrow `*slice` as mutable more than once at a time
  --> main.rs:17:11
   |
16 |     (&mut slice[..mid],
   |           ----- first mutable borrow occurs here
17 |      &mut slice[mid..])
   |           ^^^^^ second mutable borrow occurs here
18 | }
   | - first borrow ends here

 2つのスライスは重複せず別々の領域なので単一である。複数にはならず可変変数は単一。だがコンパイラにはそれがわからない。

 解決するには以下。

use std::slice;
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.offset(mid as isize), len - mid))
    }
}
  • std::slice
    • as_mut_ptr(): 生ポインタ*mut i32を返す
    • from_raw_parts_mut(): スライス生成
    • offset(): 位置を変えたポインタを返す

 関数自体はunsafeでなく、一部だけがunsafeである。

 以下のコードは未定義動作に陥る。指定アドレスのメモリは確保されていないから。でも、私の環境ではコンパイルも実行もできたし、パニックもせず終了した。

use std::slice;
fn main() {
    let address = 0x012345usize;
    let r = address as *mut i32;
    let slice = unsafe {
        slice::from_raw_parts_mut(r, 10000)
    };
}

extern: 外部コード呼出

 他言語で実装されたコードを呼び出す。

extern "C" {
    fn abs(input: i32) -> i32;
}
fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}
$ rustc main.rs
$ ./main
Absolute value of -3 according to C: 3

 上記は、C言語の標準ライブラリからabs関数を呼ぶ例。

 externで宣言された関数はすべてunsafeである。

他言語からRustコードを呼ぶ

#[no_mangle]
pub extern "C" fn call_from_c() {
     // CからRust関数を呼び出したばかり!
    println!("Just called a Rust function from C!");
}

 C言語でバイナリをリンクしたあと、call_from_c()関数を呼び出せる。

静的な変数

static HELLO_WORLD: &str = "Hello, world!";
fn main() {
    println!("name is: {}", HELLO_WORLD);
}

 静的変数は以下の特徴がある。

  • 慣習: 名前は大文字のスネークケース
  • 型注釈が必要
  • ライフタイムは'staticである(プログラム終了するまで生存)
  • メモリアドレスは固定される
  • static mutで可変にできる
  • static mutはスレッド安全でない

 定数との違い。静的変数はメモリアドレスが固定され常に同一データにアクセスする。一方、定数は使用するたびにデータを複製する。

static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}
fn main() {
    add_to_count(3);
    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}
$ rustc main.rs
$ ./main
COUNTER: 3

unsafeなトレイトを実装する

unsafe trait Foo {
    // メソッドがここに来る
}
unsafe impl Foo for i32 {
    // メソッドの実装がここに来る
}

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで