第18章: unsafe Rust

学習目標

  • unsafeの意味と使い所を理解する
  • rawポインタの使い方を学ぶ
  • FFI(Foreign Function Interface)を理解する
  • Cライブラリとの連携方法を学ぶ

---

18.1 unsafeとは

18.1.1 安全性の境界

┌─────────────────────────────────────────────────────────┐
│          Safe Rust vs Unsafe Rust                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  【Safe Rust】                                           │
│  - コンパイラが安全性を保証                              │
│  - メモリ安全性、データ競合なし                          │
│  - 99%のコードはこれで書ける                             │
│                                                         │
│  【Unsafe Rust】                                         │
│  - プログラマが安全性を保証                              │
│  - コンパイラのチェックをバイパス                        │
│  - 低レベル操作、FFI、パフォーマンス最適化で必要         │
│                                                         │
└─────────────────────────────────────────────────────────┘

18.1.2 unsafeでできること

unsafeブロック内では、以下の5つの「スーパーパワー」が使えます:

1. rawポインタのデリファレンス
2. unsafeな関数/メソッドの呼び出し
3. staticな可変変数へのアクセス/変更
4. unsafeなトレイトの実装
5. union のフィールドへのアクセス

---

18.2 rawポインタ

18.2.1 rawポインタの基本

fn main() {
    let mut num = 5;

    // rawポインタの作成(safe)
    let r1 = &num as *const i32;  // 不変rawポインタ
    let r2 = &mut num as *mut i32;  // 可変rawポインタ

    // デリファレンス(unsafe)
    unsafe {
        println!("r1: {}", *r1);
        println!("r2: {}", *r2);
    }
}

rawポインタの特徴

fn main() {
    let mut num = 5;

    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    // rawポインタは共存できる(通常の参照では不可)
    println!("r1: {:p}, r2: {:p}", r1, r2);

    // nullポインタも作れる
    let null_ptr: *const i32 = std::ptr::null();

    // 任意のアドレスも指せる(危険!)
    let dangerous_ptr = 0x12345678usize as *const i32;
}

18.2.2 rawポインタの使用例

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    // 配列を2つのスライスに分割
    let ptr = v.as_mut_ptr();
    let len = v.len();

    unsafe {
        let first_half = std::slice::from_raw_parts_mut(ptr, len / 2);
        let second_half = std::slice::from_raw_parts_mut(
            ptr.add(len / 2),
            len - len / 2
        );

        first_half[0] = 10;
        second_half[0] = 20;
    }

    println!("{:?}", v);  // [10, 2, 20, 4, 5]
}

---

18.3 unsafe関数とメソッド

18.3.1 unsafe関数の定義

unsafe fn dangerous() {
    // unsafeな操作
}

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

18.3.2 safeな抽象化の作成

// unsafe関数だが、safeなAPIを提供
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 {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];

    let (left, right) = split_at_mut(&mut v, 3);
    println!("Left: {:?}, Right: {:?}", left, right);
}

---

18.4 extern関数とFFI

18.4.1 Cの関数を呼び出す

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

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

18.4.2 Rustの関数をCから呼び出す

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Called from C!");
}

18.4.3 構造体の共有

// C互換の構造体
#[repr(C)]
struct Point {
    x: i32,
    y: i32,
}

extern "C" {
    fn process_point(point: Point);
}

fn main() {
    let point = Point { x: 10, y: 20 };
    unsafe {
        process_point(point);
    }
}

---

18.5 実践例:Cライブラリの呼び出し

18.5.1 libcの使用

use std::ffi::CString;
use std::os::raw::c_char;

extern "C" {
    fn strlen(s: *const c_char) -> usize;
}

fn main() {
    let c_str = CString::new("Hello, World!").unwrap();

    unsafe {
        let length = strlen(c_str.as_ptr());
        println!("Length: {}", length);  // 13
    }
}

18.5.2 複雑な例:構造体とメモリ管理

use std::ffi::CStr;
use std::os::raw::c_char;

#[repr(C)]
struct Person {
    name: *const c_char,
    age: u32,
}

extern "C" {
    fn create_person(name: *const c_char, age: u32) -> *mut Person;
    fn free_person(person: *mut Person);
}

fn main() {
    unsafe {
        let name = CStr::from_bytes_with_nul(b"Alice\0").unwrap();
        let person = create_person(name.as_ptr(), 25);

        if !person.is_null() {
            println!("Created person");
            free_person(person);
        }
    }
}

---

18.6 グローバル可変変数

18.6.1 static変数

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("{}", HELLO_WORLD);
}

18.6.2 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);
    }
}

注意: static mutは非常に危険!可能ならMutexAtomicUsizeを使う。

---

18.7 unsafeトレイト

18.7.1 unsafeトレイトの定義

unsafe trait Foo {
    // メソッド
}

unsafe impl Foo for i32 {
    // 実装
}

18.7.2 実例:Send / Sync

// SendとSyncはunsafeトレイト
unsafe impl Send for MyType {}
unsafe impl Sync for MyType {}

struct MyType {
    // rawポインタなど
    ptr: *mut i32,
}

---

18.8 union

18.8.1 unionの基本

#[repr(C)]
union MyUnion {
    f1: u32,
    f2: f32,
}

fn main() {
    let u = MyUnion { f1: 1 };

    unsafe {
        println!("u.f1: {}", u.f1);
        // println!("u.f2: {}", u.f2);  // 未定義動作の可能性
    }
}

18.8.2 Tagged Union

#[repr(C)]
struct Tagged {
    tag: u8,
    data: Data,
}

#[repr(C)]
union Data {
    i: i32,
    f: f32,
}

fn main() {
    let tagged = Tagged {
        tag: 0,
        data: Data { i: 42 },
    };

    unsafe {
        if tagged.tag == 0 {
            println!("Integer: {}", tagged.data.i);
        }
    }
}

---

18.9 インラインアセンブリ

18.9.1 asm!マクロ

use std::arch::asm;

fn main() {
    let x: u64 = 3;
    let y: u64;

    unsafe {
        asm!(
            "mov {0}, {1}",
            "add {0}, {0}",
            out(reg) y,
            in(reg) x,
        );
    }

    println!("x: {}, y: {}", x, y);  // x: 3, y: 6
}

---

18.10 実践例:バインディングの作成

18.10.1 簡単なCライブラリのバインディング

C側(mylib.c):

#include <stdio.h>

void greet(const char* name) {
    printf("Hello, %s!\n", name);
}

int add(int a, int b) {
    return a + b;
}

Rust側:

use std::ffi::CString;
use std::os::raw::c_char;

#[link(name = "mylib")]
extern "C" {
    fn greet(name: *const c_char);
    fn add(a: i32, b: i32) -> i32;
}

fn main() {
    unsafe {
        let name = CString::new("Rust").unwrap();
        greet(name.as_ptr());

        let result = add(5, 3);
        println!("5 + 3 = {}", result);
    }
}

18.10.2 bindgenを使った自動バインディング

[build-dependencies]
bindgen = "0.69"

build.rs:

extern crate bindgen;

use std::env;
use std::path::PathBuf;

fn main() {
    println!("cargo:rustc-link-lib=mylib");
    println!("cargo:rerun-if-changed=wrapper.h");

    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

---

18.11 unsafeのベストプラクティス

18.11.1 最小権限の原則

// 悪い例:unsafeブロックが大きすぎる
unsafe {
    // 100行のコード
}

// 良い例:unsafeは最小限に
let ptr = some_safe_operation();
let value = unsafe { *ptr };
more_safe_operations(value);

18.11.2 不変条件の文書化

/// # Safety
///
/// `ptr`は有効なメモリを指していなければならない
/// `len`は正確な長さでなければならない
unsafe fn from_raw_parts(ptr: *const u8, len: usize) -> &[u8] {
    std::slice::from_raw_parts(ptr, len)
}

18.11.3 safeな抽象化を提供

pub struct MyVec<T> {
    ptr: *mut T,
    len: usize,
    capacity: usize,
}

impl<T> MyVec<T> {
    // 内部ではunsafeを使うが、公開APIはsafe
    pub fn new() -> Self {
        MyVec {
            ptr: std::ptr::null_mut(),
            len: 0,
            capacity: 0,
        }
    }

    pub fn push(&mut self, value: T) {
        // unsafe操作を内部で使用
        unsafe {
            // ...
        }
    }
}

---

18.12 まとめ

unsafeの使い所

┌─────────────────────────────────────────────────────────┐
│          unsafeを使うべき場面                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ✓ FFI(Cライブラリとの連携)                            │
│  ✓ 低レベルな最適化                                      │
│  ✓ データ構造の実装(Vec、HashMap等)                    │
│  ✓ ハードウェア直接操作                                  │
│  ✓ インラインアセンブリ                                  │
│                                                         │
│  【避けるべき使い方】                                    │
│                                                         │
│  ✗ 「コンパイラエラーを回避する」ため                    │
│  ✗ 「面倒な借用チェックを避ける」ため                    │
│  ✗ 理由なくunsafeを使う                                  │
│                                                         │
└─────────────────────────────────────────────────────────┘

安全性の保証

1. unsafeは最小限に
2. 不変条件を明確に文書化
3. safeな抽象化を提供
4. 徹底的にテスト
5. 必要なら形式検証

---

18.13 コース全体のまとめ

おめでとうございます!Rust Foundationsコースを完了しました。

学んだこと

第1-6章: Rust基礎
  - 所有権、ライフタイム、型システム
  - エラーハンドリング、メモリ管理

第7-12章: 実践的なRust
  - データ構造、トレイト、モジュール
  - テスト、ツーリング、ベストプラクティス

第13-18章: 高度なRust
  - イテレータ、クロージャ、スマートポインタ
  - 並行処理、非同期プログラミング、unsafe

次のステップ

  • 実際のプロジェクトを作る
- CLI ツール - Webサーバー - システムプログラム

  • コミュニティに参加
- Rust Users Forum - Discord - GitHub でOSSに貢献

  • さらに学ぶ
- "The Rustonomicon"(unsafeの詳細) - "Async Book"(非同期の深掘り) - 専門分野(組み込み、WebAssembly等)

Happy Rusting! 🦀

---

参考資料