Ch 17: CStr与CString
Rust与其他语言(尤其是C)的交互需要使用FFI(Foreign Function Interface)。C语言使用null终止的字符串,Rust的String和&str不是null终止的。CString和CStr就是为此设计的:
- CString - 拥有所有权的null终止字符串,用于传递给C函数
- CStr - null终止字符串的引用,用于从C接收字符串
2. 创建CString
Section titled “2. 创建CString”use std::ffi::{CString, CStr};use std::os::raw::c_char;
fn main() { // 从&str创建CString(自动添加null终止符) let c_string = CString::new("hello").unwrap(); println!("CString: {:?}", c_string);
// 获取原始指针 let ptr: *const c_char = c_string.as_ptr(); println!("指针: {:?}", ptr);
// 转为字节形式 let bytes: &[u8] = c_string.as_bytes(); println!("字节: {:?}", bytes);
// 注意:字节中不包含结尾的null let bytes_with_nul: &[u8] = c_string.as_bytes_with_nul(); println!("带null字节: {:?}", bytes_with_nul);}3. CString与C交互
Section titled “3. CString与C交互”3.1 调用C函数
Section titled “3.1 调用C函数”use std::ffi::{CString, CStr};use std::os::raw::c_char;
fn main() { // 模拟C函数:int strlen(const char* s) extern "C" { fn strlen(s: *const c_char) -> usize; }
let s = CString::new("hello").unwrap(); // 安全调用C函数 unsafe { let len = strlen(s.as_ptr()); println!("strlen: {}", len); // 输出: 5 }}3.2 传递字符串给C
Section titled “3.2 传递字符串给C”use std::ffi::CString;use std::process::Command;
fn main() { // 使用CString调用系统命令 let program = CString::new("ls").unwrap(); let arg = CString::new("-la").unwrap();
// 很多系统API接受*Cmut类型的参数用于输出 // 这是与C交互的常见模式}4. 接收C返回的字符串
Section titled “4. 接收C返回的字符串”4.1 从C接收字符串
Section titled “4.1 从C接收字符串”use std::ffi::{CStr, CString};use std::os::raw::c_char;use std::str;
fn main() { // 模拟从C函数获取字符串 extern "C" { fn get_greeting() -> *const c_char; }
unsafe { let ptr = get_greeting(); if ptr.is_null() { println!("返回了null指针"); return; }
// 从指针创建CStr let c_str = CStr::from_ptr(ptr); println!("CStr: {:?}", c_str);
// 转换为&str let str_slice: &str = c_str.to_str().unwrap(); println!("&str: {}", str_slice);
// 或转换为String let string: String = c_str.to_string_lossy().into_owned(); println!("String: {}", string); }}4.2 to_string_lossy
Section titled “4.2 to_string_lossy”use std::ffi::CStr;
fn main() { // 模拟无效UTF-8 let invalid_bytes: &[u8] = b"hello\xff\xfeworld"; let c_str = unsafe { // 实际中CStr应从C指针创建,这里演示to_string_lossy CStr::from_bytes_with_nul(invalid_bytes).unwrap() };
// to_string_lossy用U+FFFD替换无效字符 let lossy_string = c_str.to_string_lossy(); println!("lossy: {}", lossy_string);}5. CStr方法详解
Section titled “5. CStr方法详解”| 方法 | 说明 |
|---|---|
from_ptr(ptr) | 从C指针创建CStr |
from_bytes_with_nul(bytes) | 从字节数组创建(包含null) |
to_str() | 转换为&str(可能失败) |
to_string_lossy() | 转换为String,替换无效字符 |
to_bytes() | 转换为字节(不含null) |
to_bytes_with_nul() | 转换为字节(含null) |
len() | 字节长度(不含null) |
is_empty() | 是否为空 |
6. 注意事项
Section titled “6. 注意事项”6.1 避免内存泄漏
Section titled “6.1 避免内存泄漏”use std::ffi::CString;
fn main() { // CString会在drop时释放内存 // 但如果直接获取原始指针并忘记,CString drop后会造成问题
let c_string = CString::new("hello").unwrap(); let ptr = c_string.as_ptr();
// 错误:如果在这里让c_string drop,指针将无效 // drop(c_string); // unsafe { println!("{}", *ptr); } // 未定义行为!
// 正确做法:保持CString活着,或复制数据 println!("指针指向: {:?}", unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy() });}6.2 空字符串情况
Section titled “6.2 空字符串情况”use std::ffi::CString;
fn main() { // 空字符串 let empty = CString::new("").unwrap(); println!("空CString字节: {:?}", empty.as_bytes_with_nul()); // 输出: [0]
// 包含null字符(内部包含) let with_null = CString::new("a\x00b").unwrap(); println!("带null字节: {:?}", with_null.as_bytes_with_nul()); // 输出: [97, 0, 98, 0]}7. Box 堆分配
Section titled “7. Box 堆分配”use std::ffi::CStr;use std::os::raw::c_char;
fn main() { // Box<CStr> 将CStr堆分配 let boxed: Box<CStr> = unsafe { let c_str = CStr::from_bytes_with_nul(b"hello\0").unwrap(); Box::new(c_str) };
println!("boxed: {:?}", boxed);
// CStr不支持直接Box::new,需要这样 // 更常见的是从C函数返回的指针Box}8. 实际应用示例
Section titled “8. 实际应用示例”8.1 与系统库交互
Section titled “8.1 与系统库交互”use std::ffi::CString;use std::os::raw::c_char;
#[repr(C)]pub struct Dirent { pub d_name: *const c_char,}
fn main() { // 实际使用中,如POSIX的readdir返回的Dirent结构 // d_name字段是指向null终止字符串的指针 println!("使用CStr处理来自C库的字符串");}8.2 错误处理
Section titled “8.2 错误处理”use std::ffi::{CString, CStr};use std::ptr;
fn main() { // C函数可能返回null表示错误 // 正确处理null检查
unsafe fn get_c_string() -> *const c_char { ptr::null() // 模拟返回null }
let result = unsafe { let ptr = get_c_string(); if ptr.is_null() { None } else { Some(CStr::from_ptr(ptr).to_string_lossy().into_owned()) } };
match result { Some(s) => println!("Got: {}", s), None => println!("Got null"), }}今天我们学习了CStr和CString:
- CString - 拥有所有权的null终止字符串,用于传递给C函数
- CStr - null终止字符串的引用,用于从C接收字符串
- 创建方式 -
CString::new()从&str创建 - 转换方法 -
to_str()、to_string_lossy()、as_ptr() - 注意事项 - 内存所有权、null指针检查、无效UTF-8处理
FFI互操作需要小心处理内存所有权和生命周期,正确使用CString和CStr可以安全地与C代码交互。