Лучший способ работать со строками C в Rust - использовать структуры из модуля std::ffi
, а именно CStr
и _ 3_.
CStr
- это тип с динамическим размером, поэтому его можно использовать только через указатель. Это делает его очень похожим на обычный тип str
. Вы можете построить &CStr
из *const c_char
, используя небезопасный _8 _ статический метод. Этот метод небезопасен, потому что нет гарантии, что переданный ему необработанный указатель действителен, что он действительно указывает на допустимую строку C и что время жизни строки правильное.
Вы можете получить &str
из &CStr
, используя его _11 _ метод.
Вот пример:
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(); // if necessary
}
Вы должны учитывать время жизни ваших *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 _27 _ / _ 28_ - с точностью до наоборот - они не заканчиваются нулевым байтом и могут содержать произвольное их количество внутри. Это означает, что переход от Vec<u8>
к CString
не является ни безошибочным, ни без выделения - конструктор CString
проверяет наличие нулей внутри предоставленных вами данных, возвращая ошибку, если обнаруживает их, и добавляет нулевой байт в конец байта. вектор, который может потребовать его перераспределения.
Подобно String
, который реализует Deref<Target = str>
, CString
реализует Deref<Target = CStr>
, поэтому вы можете вызывать методы, определенные в CStr
, непосредственно в CString
. Это важно, потому что метод as_ptr()
, который возвращает *const c_char
, необходимый для взаимодействия с C, определен на 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(); // from a &str, creates a new allocation
let c_str_2 = CString::new(b"world" as &[u8]).unwrap(); // from a &[u8], creates a new allocation
let data: Vec<u8> = b"12345678".to_vec(); // from a Vec<u8>, consumes it
let c_str_3 = CString::new(data).unwrap();
// and now you can obtain a pointer to a valid zero-terminated string
// make sure you don't use it after c_str_2 is dropped
let c_ptr: *const c_char = c_str_2.as_ptr();
// the following will print an error message because the source data
// contains zero bytes
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, вы можете вызвать _ 53_. Затем вам необходимо вернуть указатель и освободить его в Rust; распределитель Rust вряд ли будет таким же, как распределитель, используемый malloc
и free
. Все, что вам нужно сделать, это вызвать CString::from_raw
и затем разрешите сбросить строку в обычном режиме.
person
Vladimir Matveev
schedule
10.06.2014