メモリ非安全コードを書く。
成果物
参考
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 { // メソッドの実装がここに来る }
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
bash 4.4.12(1)-release
rustc 1.34.2 (6c2484dc3 2019-05-13)
- cargo 1.34.0 (6789d8a0a 2019-04-01)
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux
前回まで
- Rustを学んでみたい(プログラミング言語)
- Rustの環境構築
- RustでHelloWorld
- Rustの和訳ドキュメント
- Cargoでプロジェクト作成・ビルド・実行
- クレートとは?
- Rustで関数を使ってみる
- Rustでモジュールを使ってみる
- Rustで乱数を生成する(rand)
- Rustで標準入力する(std::io::stdin().read_line())
- RustでMatch判定する(match)
- Rustでprintとread_lineを1行にする方法
- Rustで数当てゲーム
- クレート名にドット.が使えない
- Rustの変数と可変性(let, mut) error[E0384]: cannot assign twice to immutable variable
x
- Rustのimmutable束縛とconst定数の違い
- RustのREPL、evcxrのインストールに失敗した
- Rustでコンパイルするときの変数未使用warningを消す
- Rustの変数(再代入、再宣言(シャドーイング))
- Rustのシャドーイングについて
- イミュータブルについて(副作用)
- Rustの定数(const)
- Rustのデータ型(数値)
- Rustのデータ型(論理)
- Rustのデータ型(文字)
- Rustのデータ型(タプル)
- Rustのデータ型(配列)
- Rustの関数
- Rustのif式
- Rustのくりかえし文(loop)
- Rustのくりかえし文(while)
- Rustのくりかえし文(for)
- Rustの所有権(ムーブ)
- Rustの所有権(関数)
- Rustの所有権(スライス)
- Rustの構造体(定義とインスタンス化)
- Rustの構造体(プログラム例)
- Rustの構造体(メソッド)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustのmatch(制御フロー演算子)
- RustでNULLを扱う(Option, Some, None)
- NULL参照は10億ドルの失敗だった
- Rustの列挙型に独自表示を実装する(E0277 対策 std::fmt::Display 実装)
- RustのIfLet(matchの糖衣構文)
- Rustのプロジェクト構造
- Rustのcargoでライブラリ&テスト(単体、結合)
- Rustのモジュール(mod)
- Rustのモジュール(pub)
- Rustのmod参照方法(
mod 子モジュール名;
,use 要素名;
,extern crate クレート名;
,super
) - Rustのインポートまとめ(Rust2018)
- RustのコレクションVec型
- RustのコレクションString型
- RustのコレクションHashMap型
- Rustのコレクション(練習問題)
- Rustのエラー処理
- Rustのジェネリクス
- Rustのトレイト
- Rustのライフタイム1
- Rustのライフタイム2(構造体の定義)
- Rustのライフタイム3(ライフタイム省略)
- Rustのライフタイム4(impl定義)
- Rustの静的ライフタイム5('static)
- Rustのライフタイム6(ジェネリクス、トレイト境界とともに)
- Rustのテストコードを書く
- Rustのテスト実行
- Rustのテスト体系化
- Rustでコマンドライン引数を受け取る
- Rustのファイル読込
- Rustでリファクタリング(モジュール性とエラー処理の向上)
- Rustでテスト駆動開発
- Rustで環境変数を取得する
- RustでStdErr出力
- Rustのクロージャ
- Rustのイテレータ
- Rustのイテレータ(Minigrep改善)
- Rustのイテレータ(パフォーマンス)
- Rustのイテレータ(Minigrep改善)
- Rustのcargo(ビルドのカスタマイズ)
- Rustのcargo(cargo docでドキュメント生成)
- Rustのエクスポート(pub use)
- Rustのクレートを公開する方法(crates.io)
- Rustのcargoでワークスペースをつくる
- Rustのcargo installでバイナリをインストールする
- Rustのcargoを拡張する方法
- Rustのスマートポインタ
- スマートポインタBox
- Rustのスマートポインタ(Derefトレイト)
- Rustのスマートポインタ(Dropトレイト)
- Rustのスマートポインタ(Rc
) - Rustのスマートポインタ(RefCell
) - Rustのスマートポインタ(Weak
) - Rustのスレッド
- Rustのスレッド(メッセージ送受信)
- Rustのスレッド(Mutex、Arc)
- Rustのスレッド(Send、Syncトレイト)
- Rustのオブジェクト指向
- RustのOOP(トレイトオブジェクト)
- Rustのオブジェクト指向(デザインパターン)
- Rustのパターン
- Rustのパターン(論駁可能性)
- Rustパターン(記法)