Ответ 1
Лучший способ работать со строками C в Rust - использовать структуры из std::ffi
, а именно CStr
и CString
.
CStr
является типом динамического размера и поэтому его можно использовать только с помощью указателя. Это делает его очень похожим на обычный тип str
. Вы можете построить &CStr
из *const c_char
, используя небезопасный CStr::from_ptr
статический метод. Этот метод небезопасен, потому что нет гарантии, что необработанный указатель, который вы передаете ему, действителен, что он действительно указывает на допустимую строку C и что время жизни строки верное.
Вы можете получить &str
из &CStr
, используя метод to_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(); // if necessary
}
Вам нужно учитывать срок службы ваших указателей *const c_char
и кто их владеет. В зависимости от C API вам может потребоваться вызвать специальную функцию освобождения от строки. Вам нужно тщательно организовать конверсии, чтобы срезы не пережить указатель. Тот факт, что CStr::from_ptr
возвращает a &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
. Это означает, что он "удерживает" дескриптор для распределения байтовых данных, а dropping CString
освобождает предоставленную им память (по существу, CString
обертывает Vec<u8>
, а последний - будет отброшен). Следовательно, это полезно, когда вы хотите выставить данные, выделенные в Rust, как строку C.
К сожалению, строки C всегда заканчиваются нулевым байтом и не могут содержать внутри них, в то время как Rust &[u8]
/Vec<u8>
- это совершенно противоположное - они не заканчиваются нулевым байтом и могут содержать произвольные числа их внутри. Это означает, что переход от 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, вы можете вызвать CString.html::into_raw
. Затем вам необходимо вернуть указатель и освободить его в Rust; распределитель Rust вряд ли будет таким же, как распределитель, используемый malloc
и free
. Все, что вам нужно сделать, это вызвать CString::from_raw
, а затем разрешить нормальную передачу строки.