第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は非常に危険!可能ならMutexやAtomicUsizeを使う。
---
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
次のステップ
- 実際のプロジェクトを作る
- コミュニティに参加
- さらに学ぶ
Happy Rusting! 🦀
---