S-JIS[2024-09-26] 変更履歴

Rust c_char, CStr, CString

Ruststd::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の概要

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()は可変・不変に関係ない。


CStrの概要

関数の引数として文字列を受け取る場合、c_charのポインターを受け取る。
それをCStrに渡してRust内で文字列を扱う。

CStrは関数の外からポインター(参照)を借用している、と言えるかもしれない。

文字列を受け取る関数の例


CStringの概要

関数の返り値として文字列を返す場合、c_charのポインターを返す。
そのために、Rust内の文字列を*c_charに変換するのがCString。

CStringは文字列の為のメモリーを確保して所有する。
関数の呼び出し元へは、そのポインター(参照)を貸している、と言えるかもしれない。

所有しているメモリーはどこかで解放する必要があるので、解放するための関数を別途用意し、それを呼び出してもらうという形にする。

文字列を返す関数の例


文字列を受け取る関数の例

関数で文字列(UTF-8でnul終端)を受け取る例。[2024-09-25]

src/lib.rs:

#[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]

src/lib.rs:

#[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→&strString)→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();

stdへ戻る / Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま