前提 #
所与の条件として、下記の C/C++ の関数が存在するとします。
ヘッダーファイル(C および C++ 両対応):hello.h
#
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void hello(const char *text);
#ifdef __cplusplus
}
#endif実装ファイル:hello.c または hello.cpp
#
#include <stdio.h>
#include "hello.h"
void hello(const char *text) {
printf("%s\n", text);
}コンパイル #
C としてコンパイルする場合
gcc -c hello.c -o hello.oC++ としてコンパイルする場合
g++ -c hello.cpp -o hello.oRust から呼び出すコードを書いてみる #
さて、コンパイルされた C/C++ ライブラリを呼び出す Rust のコードを書いてみましょう。
main.rs
use std::ffi::CString;
unsafe extern "C" {
fn hello(text: *const char);
}
fn main() {
let text = CString::new("hello").unwrap();
unsafe {
hello(text.as_ptr());
}
}パッと上記が思いつきそうですが、これは間違っています。なぜなら、C の char 型は Rust の char 型とは異なるためです。名称は同じながら別の型です。
C の char は 1 バイトの整数型ですが、Rust の char は Unicode 文字を表す型です。両者に互換性はありません。
では C の char に対応する Rust の型は何でしょうか。C の char は 1 バイトの整数型なので、Rust の i8 または u8 が思い浮かぶかもしれません。
use std::ffi::CString;
unsafe extern "C" {
fn hello(text: *const i8);
// または fn hello(text: *const u8);
}
fn main() {
let text = CString::new("hello").unwrap();
unsafe {
hello(text.as_ptr());
}
}ここで、Rust の i8 は符号付き整数、u8 は符号なし整数です。
では、C の char はどちらでしょうか?実は、C の plain char が符号付きか符号なしのどちらかで扱われるかは実装依存(implementation-defined)です。つまり、環境によって異なります。
Which of signed char or unsigned char has the same range, representation, and behavior as “plain” char.
Determined by ABI.
https://gcc.gnu.org/onlinedocs/gcc/Characters-implementation.html
「Determined by ABI」の ABI は Application Binary Interface(アプリケーション・バイナリ・インターフェース)の略です。要するにコンパイル時に指定するターゲット環境(ターゲットトリプル)によって、C の char が符号付きか符号なしが分かれるということです。
より具体的に、よく使われるであろう Rust のターゲットトリプルを例にあげると、x86_64-unknown-linux-gnu では C の char は符号付きの signed char であり、aarch64-unknown-linux-gnu では C の char は符号なしの unsigned char になります。
従って、text.as_ptr() が *const i8 を返すのか *const u8 を返すのかは、ターゲット環境によって異なります。
text.as_ptr() が *const u8 を返す環境の場合、extern ブロックの関数宣言が fn hello(text: *const i8); となっていると型が合いません。逆に、text.as_ptr() が *const i8 を返す環境の場合、extern ブロックの関数宣言が fn hello(text: *const u8); となっていると、同様に方が合いません。その結果、cargo check や cargo build 時にコンパイルエラーになります。
c_char を使おう
#
ということで、ターゲット環境によって C の char が符号付きか符号なしのどちらになるかを気にせずに、Rust から C の char を扱うための型が用意されています。それが c_char です。
- c_char in std::ffi - Rust
- https://doc.rust-lang.org/std/ffi/type.c_char.html
c_char はターゲット環境に応じて i8 または u8 のどちらかに切り替わる型エイリアスです。
use std::ffi::c_char;
use std::ffi::CString;
unsafe extern "C" {
fn hello(text: *const c_char);
}
fn main() {
let text = CString::new("hello").unwrap();
unsafe {
hello(text.as_ptr());
}
}リンキングと実行 #
C/C++ のオブジェクトファイルとリンクして、Rust の実行ファイルを作成する。
rustc --edition 2024 main.rs -C link-arg=hello.o実行する。
./main