使用
std::ffi
模块中的结构体
CStr
和
CString
是在Rust中处理C字符串的最佳方法。
CStr
是一种动态大小类型,因此只能通过指针使用。这使它与常规的
str
类型非常相似。您可以使用不安全的
CStr::from_ptr
静态方法从
*const c_char
构造
&CStr
。这个方法是不安全的,因为无法保证您传递给它的原始指针是有效的,它确实指向一个有效的C字符串,并且该字符串的生命周期是正确的。
你可以通过
&CStr
的
to_str()
方法来获取
&str
。
以下是一个例子:
extern crate libc;
use libc::c_char;
use std::ffi::CStr;
use std::str;
extern {
fn hello() -> *const c_char;
}
fn main() {
let c_buf: *const c_char = unsafe { hello() };
let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
let str_slice: &str = c_str.to_str().unwrap();
let str_buf: String = str_slice.to_owned();
}
你需要考虑你的
*const c_char
指针的生命周期和所有者。根据C API,你可能需要在字符串上调用特殊的释放函数。你需要仔细安排转换,以便切片不会超出指针的生命周期。
CStr::from_ptr
返回具有任意生命周期的
&CStr
在这里有所帮助(虽然它本身很危险);例如,你可以将你的C字符串封装到一个结构中,并提供一个
Deref
转换,这样你就可以像使用字符串切片一样使用你的结构:
extern crate libc;
use libc::c_char;
use std::ops::Deref;
use std::ffi::CStr;
extern "C" {
fn hello() -> *const c_char;
fn goodbye(s: *const c_char);
}
struct Greeting {
message: *const c_char,
}
impl Drop for Greeting {
fn drop(&mut self) {
unsafe {
goodbye(self.message);
}
}
}
impl Greeting {
fn new() -> Greeting {
Greeting { message: unsafe { hello() } }
}
}
impl Deref for Greeting {
type Target = str;
fn deref<'a>(&'a self) -> &'a str {
let c_str = unsafe { CStr::from_ptr(self.message) };
c_str.to_str().unwrap()
}
}
这个模块中还有另一种类型,称为CString
。它与CStr
的关系与String
与str
的关系相同 - CString
是CStr
的拥有版本。这意味着它“持有”分配的字节数据的句柄,放弃CString
会释放它提供的内存(实际上,CString
包装了Vec<u8>
,而后者将被删除)。因此,当您想要将在Rust中分配的数据公开为C字符串时,它非常有用。
抱歉,C字符串总是以零字节结尾,不能在其内部包含一个零字节,而Rust的
&[u8]
/
Vec<u8>
恰好相反 - 它们不以零字节结尾,并且可以包含任意数量的零字节。这意味着从
Vec<u8>
转换为
CString
既不是无错误的,也不是无需分配内存的——
CString
构造函数会检查您提供的数据中是否存在零字节,如果发现,则返回错误,并将零字节附加到字节向量的末尾,这可能需要重新分配其内存。
与 String
类似,它实现了 Deref<Target = str>
,CString
实现了 Deref<Target = CStr>
,因此您可以在 CString
上直接调用在 CStr
上定义的方法。这很重要,因为返回用于 C 交互的 *const c_char
的 as_ptr()
方法是在 CStr
上定义的。您可以直接在 CString
值上调用此方法,这非常方便。
CString
可以从任何可以转换为 Vec<u8>
的内容创建。 String
、&str
、Vec<u8>
和 &[u8]
都是构造函数 CString::new()
的有效参数。当然,如果传递一个字节切片或字符串切片,则会创建一个新的分配,而 Vec<u8>
或 String
将被消耗。
extern crate libc;
use libc::c_char;
use std::ffi::CString;
fn main() {
let c_str_1 = CString::new("hello").unwrap();
let c_str_2 = CString::new(b"world" as &[u8]).unwrap();
let data: Vec<u8> = b"12345678".to_vec();
let c_str_3 = CString::new(data).unwrap();
let c_ptr: *const c_char = c_str_2.as_ptr();
let data: Vec<u8> = vec![1, 2, 3, 0, 4, 5, 0, 6];
match CString::new(data) {
Ok(c_str_4) => println!("Got a C string: {:p}", c_str_4.as_ptr()),
Err(e) => println!("Error getting a C string: {}", e),
}
}
如果您需要将
CString
的所有权转移给 C 代码,可以调用
CString::into_raw
。然后您需要在 Rust 中获取指针并释放它;Rust 分配器可能与
malloc
和
free
使用的分配器不同。您只需要调用
CString::from_raw
然后允许字符串正常删除即可。