Rustのstd::ffi::c_char, CStr, CStringのメモ。
|
C ABI形式でRust以外の言語から関数が呼ばれるときの、文字列の引数や返り値にc_char型を使う。
そして、Rustの外から受け取った文字列(ポインター)をRust内で扱えるようにするのがCStr構造体。
Rustの文字列を関数から(ポインターとして)返せるようにするのがCString構造体。
use std::ffi::{c_char, CStr, CString};
c_char, CStr, CStringのいずれも、UTF-8でnul終端の文字列を扱う。
(CStr, CStringという名前は、Rustの標準的な文字列を表すstr, Stringと対比させているらしい)
c_charは、具体的には「*const c_char
」といった形で使う。
*c_charは言わばポインターそのものなので、nullを保持することが出来る。
let s: *const c_char = std::ptr::null(); if s.is_null() { println!("null"); }
let mut s: *mut c_char = std::ptr::null_mut(); if s.is_null() { println!("null"); }
通常の変数はmutが付いていなければ不変だが、*c_charの型名には、不変ならconstを付ける必要があるようだ。
*c_charの変数が不変(const)か可変(mut)かによって、代入するnullは異なる。
判定のis_null()は可変・不変に関係ない。
関数の引数として文字列を受け取る場合、c_charのポインターを受け取る。
それをCStrに渡してRust内で文字列を扱う。
CStrは関数の外からポインター(参照)を借用している、と言えるかもしれない。
関数の返り値として文字列を返す場合、c_charのポインターを返す。
そのために、Rust内の文字列を*c_charに変換するのがCString。
CStringは文字列の為のメモリーを確保して所有する。
関数の呼び出し元へは、そのポインター(参照)を貸している、と言えるかもしれない。
所有しているメモリーはどこかで解放する必要があるので、解放するための関数を別途用意し、それを呼び出してもらうという形にする。
関数で文字列(UTF-8でnul終端)を受け取る例。[2024-09-25]
#[no_mangle] pub extern "C" fn my_println(s: *const c_char) { if s.is_null() { println!("rust.my_println(null)"); return; } let s: &CStr = unsafe { CStr::from_ptr(s) }; let s: &str = s.to_str().unwrap(); println!("rust.my_println: {}", s); }
文字列は「*const c_char
」型で受け取る。
それをCStr::from_ptr()で&CStrに変換するが、from_ptr()はunsafeなメソッドなので、unsafeブロックで囲む必要がある。
一行にまとめて以下のように書くことも出来る。
let s: &str = unsafe { CStr::from_ptr(s) }.to_str().unwrap();
関数から文字列(UTF-8でnul終端)を返す例。[2024-09-25]
#[no_mangle] pub extern "C" fn my_to_string(n: i32) -> *const c_char { // let s: String = n.to_string(); let s: String = format!("(rust {})", n); let s = CString::new(s).unwrap(); s.into_raw() } #[no_mangle] pub extern "C" fn free_string(s: *mut c_char) { if s.is_null() { return; } unsafe { let _ = CString::from_raw(s); } }
文字列を返す関数は、戻り値の型を「*const c_char
」にする。
そして、CStringインスタンスを作り、そこからポインターを取得(into_raw()
)してそのポインターを返す。
この文字列はRust側でメモリーを確保したので、Rust側でメモリーを解放する必要がある。
そのため、メモリーを解放する関数を用意する。
Rustの文字列を受け取った側は、必ずこの関数を呼び出す必要があるものとする。
実際のメモリー解放の手順は、CString::from_raw()
によって一旦CStringインスタンスを作り、それがスコープから外れることによってdrop()が呼ばれて解放される、という流れ。
文字列を受け取って文字列を返す場合、cc_char→CStr→&str(String)→CString→c_charの順に変換していくことになる。
#[no_mangle] pub extern "C" fn append(s1: *const c_char, s2: *const c_char) -> *const c_char { if s1.is_null() || s2.is_null() { return std::ptr::null(); } let cstr1: &CStr = unsafe { CStr::from_ptr(s1) }; let cstr2: &CStr = unsafe { CStr::from_ptr(s2) }; let str1: &str = cstr1.to_str().unwrap_or(""); let str2: &str = cstr2.to_str().unwrap_or(""); let string: String = format!("{}{}", str1, str2); let cstring = CString::new(string).unwrap(); cstring.into_raw() }
CStringでメモリーを確保してinto_raw()でポインターを返しているので、後で(free_string()を呼んでもらって)メモリーを解放する必要がある。
&strからCStringを作る場合もCString::new()でいい。
let cstring = CString::new("abc").unwrap();
&CStrからCStringを作る場合は、バイト配列を経由する方が楽か。
let cstring = CString::new(cstr.to_bytes()).unwrap();