6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

Rustは安全な言語。でも、既存のCライブラリを使いたいときがある。

  • システムコール
  • 高速な数値計算ライブラリ
  • レガシーコード

そんなとき使うのが FFI (Foreign Function Interface)

この記事では、CライブラリをRustから安全に呼び出す方法を解説します。

目次

  1. FFI の基礎
  2. extern ブロック
  3. 型のマッピング
  4. 実践:libc を使う
  5. 安全なラッパーを作る
  6. bindgen
  7. まとめ

FFI の基礎

FFI とは

Foreign Function Interface - 他の言語で書かれた関数を呼び出す仕組み。

Rust では主に C 言語との相互運用を指します。

// C の関数を Rust から呼ぶ
extern "C" {
    fn abs(x: i32) -> i32;
}

fn main() {
    unsafe {
        println!("{}", abs(-5));  // 5
    }
}

なぜ unsafe?

C言語の世界には Rust の安全性保証がない:

  • null ポインタの可能性
  • メモリ安全性の保証なし
  • ダングリングポインタの可能性
  • スレッド安全性の保証なし

だから FFI 呼び出しは unsafe ブロックが必要。

extern ブロック

基本形

extern "C" {
    fn function_name(arg1: Type1, arg2: Type2) -> ReturnType;
}
  • "C": 呼び出し規約(ほとんどの場合これ)
  • 関数のシグネチャを宣言

呼び出し規約

extern "C" { }        // C 言語の呼び出し規約(デフォルト)
extern "system" { }   // OS のシステム呼び出し規約(Windows API用)
extern "stdcall" { }  // Windows 32bit API

可変長引数

extern "C" {
    fn printf(format: *const i8, ...) -> i32;
}

型のマッピング

基本型の対応

C言語 Rust libc
int i32 c_int
unsigned int u32 c_uint
long i64 (LP64) c_long
unsigned long u64 (LP64) c_ulong
char i8 c_char
unsigned char u8 c_uchar
float f32 c_float
double f64 c_double
void () c_void
size_t usize size_t

ポインタ型

// const char* → *const c_char
// char* → *mut c_char
// void* → *mut c_void
// const void* → *const c_void

use std::os::raw::{c_char, c_void};

extern "C" {
    fn strlen(s: *const c_char) -> usize;
    fn malloc(size: usize) -> *mut c_void;
    fn free(ptr: *mut c_void);
}

構造体

// C言語
struct Point {
    int x;
    int y;
};
// Rust
#[repr(C)]
struct Point {
    x: i32,
    y: i32,
}

#[repr(C)] で C言語と同じメモリレイアウトを保証。

列挙型

// C言語
enum Color {
    RED = 0,
    GREEN = 1,
    BLUE = 2
};
// Rust
#[repr(C)]
enum Color {
    Red = 0,
    Green = 1,
    Blue = 2,
}

実践:libc を使う

セットアップ

[dependencies]
libc = "0.2"

getpid を呼ぶ

use libc::{getpid, pid_t};

fn main() {
    let pid: pid_t = unsafe { getpid() };
    println!("PID: {}", pid);
}

文字列の扱い

Rust → C

use std::ffi::CString;
use libc::{c_char, puts};

fn main() {
    let rust_string = "Hello from Rust!";
    let c_string = CString::new(rust_string).unwrap();
    
    unsafe {
        puts(c_string.as_ptr());
    }
}

C → Rust

use std::ffi::CStr;
use libc::c_char;

extern "C" {
    fn getenv(name: *const c_char) -> *const c_char;
}

fn main() {
    let key = CString::new("PATH").unwrap();
    
    unsafe {
        let value_ptr = getenv(key.as_ptr());
        if !value_ptr.is_null() {
            let value = CStr::from_ptr(value_ptr);
            println!("PATH: {:?}", value.to_str().unwrap());
        }
    }
}

メモリ操作

use libc::{malloc, free, c_void, size_t};
use std::ptr;

fn main() {
    unsafe {
        // メモリ確保
        let size: size_t = 100;
        let ptr = malloc(size);
        
        if ptr.is_null() {
            panic!("malloc failed");
        }
        
        // 使用
        let slice = std::slice::from_raw_parts_mut(ptr as *mut u8, size);
        slice[0] = 42;
        
        // 解放
        free(ptr);
    }
}

安全なラッパーを作る

FFI を直接使うのは危険。安全なラッパーを作ろう。

パターン1: Option で null チェック

use std::ffi::{CStr, CString};
use libc::c_char;

extern "C" {
    fn getenv(name: *const c_char) -> *const c_char;
}

// 安全なラッパー
fn safe_getenv(key: &str) -> Option<String> {
    let c_key = CString::new(key).ok()?;
    
    unsafe {
        let ptr = getenv(c_key.as_ptr());
        if ptr.is_null() {
            None
        } else {
            CStr::from_ptr(ptr)
                .to_str()
                .ok()
                .map(|s| s.to_string())
        }
    }
}

fn main() {
    match safe_getenv("PATH") {
        Some(path) => println!("PATH: {}", path),
        None => println!("PATH not set"),
    }
}

パターン2: Result でエラーハンドリング

use std::ffi::CString;
use std::io;
use libc::{c_char, c_int, open, close, O_RDONLY};

// 安全なラッパー
fn safe_open(path: &str) -> io::Result<i32> {
    let c_path = CString::new(path)
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Invalid path"))?;
    
    let fd = unsafe { open(c_path.as_ptr(), O_RDONLY) };
    
    if fd < 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(fd)
    }
}

fn safe_close(fd: i32) -> io::Result<()> {
    let result = unsafe { close(fd) };
    
    if result < 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(())
    }
}

fn main() -> io::Result<()> {
    let fd = safe_open("/etc/passwd")?;
    println!("Opened fd: {}", fd);
    safe_close(fd)?;
    Ok(())
}

パターン3: RAII でリソース管理

use std::ffi::CString;
use std::io;
use libc::{c_int, open, close, read, O_RDONLY};

struct File {
    fd: c_int,
}

impl File {
    fn open(path: &str) -> io::Result<Self> {
        let c_path = CString::new(path)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Invalid path"))?;
        
        let fd = unsafe { open(c_path.as_ptr(), O_RDONLY) };
        
        if fd < 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(File { fd })
        }
    }
    
    fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
        let n = unsafe {
            read(self.fd, buf.as_mut_ptr() as *mut _, buf.len())
        };
        
        if n < 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(n as usize)
        }
    }
}

impl Drop for File {
    fn drop(&mut self) {
        unsafe {
            close(self.fd);
        }
    }
}

fn main() -> io::Result<()> {
    let file = File::open("/etc/passwd")?;
    let mut buf = [0u8; 100];
    let n = file.read(&mut buf)?;
    println!("Read {} bytes", n);
    // file は自動的に close される
    Ok(())
}

bindgen

C のヘッダファイルから Rust のバインディングを自動生成!

インストール

cargo install bindgen-cli

使い方

// example.h
typedef struct {
    int x;
    int y;
} Point;

Point create_point(int x, int y);
int distance(Point* a, Point* b);
bindgen example.h -o bindings.rs

生成される bindings.rs:

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Point {
    pub x: ::std::os::raw::c_int,
    pub y: ::std::os::raw::c_int,
}

extern "C" {
    pub fn create_point(
        x: ::std::os::raw::c_int,
        y: ::std::os::raw::c_int,
    ) -> Point;
}

extern "C" {
    pub fn distance(
        a: *mut Point,
        b: *mut Point,
    ) -> ::std::os::raw::c_int;
}

build.rs で自動化

# Cargo.toml
[build-dependencies]
bindgen = "0.69"
// build.rs
use std::env;
use std::path::PathBuf;

fn main() {
    println!("cargo:rerun-if-changed=wrapper.h");
    
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .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!");
}
// src/lib.rs
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

よくある落とし穴

1. 文字列の寿命

// ❌ 危険!CString がスコープを抜けると解放される
fn bad() -> *const c_char {
    let s = CString::new("hello").unwrap();
    s.as_ptr()  // ダングリングポインタ!
}

// ✅ 安全
fn good() {
    let s = CString::new("hello").unwrap();
    unsafe {
        some_c_function(s.as_ptr());  // s はまだ生きている
    }
}  // ここで s が解放される

2. NULL ポインタチェック

// ❌ NULL チェックなし
unsafe {
    let ptr = some_c_function();
    let value = *ptr;  // NULL なら Undefined Behavior
}

// ✅ NULL チェックあり
unsafe {
    let ptr = some_c_function();
    if !ptr.is_null() {
        let value = *ptr;
    }
}

3. メモリの所有権

// C で確保したメモリは C で解放
unsafe {
    let ptr = malloc(100);
    // ... 使用 ...
    free(ptr);  // malloc したら free
}

// Rust で確保したメモリは Rust で解放
let boxed = Box::new(42);
let ptr = Box::into_raw(boxed);
unsafe {
    // C に渡す
    some_c_function(ptr);
    // 使い終わったら Rust で解放
    let _ = Box::from_raw(ptr);
}

まとめ

FFI のベストプラクティス

  1. 最小限の unsafe - unsafe ブロックは小さく
  2. 安全なラッパー - 公開 API は safe に
  3. NULL チェック - 常に行う
  4. エラーハンドリング - Result/Option を活用
  5. RAII - リソース管理を自動化
  6. bindgen - 自動生成を活用

チェックリスト

  • #[repr(C)] を構造体に付けたか
  • NULL ポインタをチェックしたか
  • 文字列の寿命は適切か
  • メモリの所有権は明確か
  • エラーハンドリングは適切か

参考リンク

FFI は unsafe の世界だけど、適切にラップすれば安全に使えます。C資産を活かしながら、Rust の安全性も享受しましょう!

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?