Najlepszym sposobem, aby pracować z ciągów C w Rust jest stosowanie struktur z modułu std::ffi
, mianowicie CStr
i CString
.
CStr
to typ o dynamicznym rozmiarze, więc można go używać tylko za pomocą wskaźnika. To sprawia, że jest bardzo podobny do zwykłego typu str
. Możesz skonstruować &CStr
z *const c_char
używając niebezpiecznej metody statycznej CStr::from_ptr
. Ta metoda jest niebezpieczna, ponieważ nie ma gwarancji, że surowy wskaźnik, który do niej trafisz, jest poprawny, że rzeczywiście wskazuje poprawny łańcuch C i że jego żywotność jest poprawna.
Można uzyskać &str
z &CStr
przy użyciu metody to_str()
.
Oto przykład:
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
}
Trzeba wziąć pod uwagę cały okres użytkowania *const c_char
wskaźników i kto posiada je. W zależności od API C, może zajść potrzeba wywołania specjalnej funkcji dealowania na łańcuchu. Musisz dokładnie ułożyć konwersje, aby plasterki nie przeżyły wskaźnika. Fakt, że CStr::from_ptr
zwraca wartość &CStr
z dowolnym czasem życia, pomaga (ale sama w sobie jest niebezpieczna); Na przykład, można upakować Twojego C ciąg do struktury i zapewnienie Deref
konwersji, dzięki czemu można używać swój struct jakby to był kawałek ciąg:
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()
}
}
Istnieje również inny typ w tym module o nazwie CString
. Ma on tę samą relację z CStr
jako String
z str
- CString
jest własnością użytkownika CStr
. Oznacza to, że "przechowuje" uchwyt do alokacji danych bajtowych, a porzucenie CString
uwolniłoby pamięć, którą zapewnia (zasadniczo CString
zawija Vec<u8>
, i to ta ostatnia, która zostanie usunięta). W związku z tym jest to przydatne, gdy chcemy ujawnić dane przydzielone w Rust jako ciąg C.
Niestety, smyczki C zawsze kończy się bajtem zerowym i nie może zawierać jeden wewnątrz nich, a Rust &[u8]
/Vec<u8>
są dokładnie odwrotnie rzecz - nie kończy się bajtem zerowym i może zawierać dowolne numery z nich w środku. Oznacza to, że przechodzenie z wersji Vec<u8>
do CString
nie jest wolne od błędów ani alokacji - konstruktor CString
sprawdza zarówno zera w podanych danych, zwracając błąd, jeśli znajdzie jakieś, i dołącza zero bajtów do końca bajtu wektor, który może wymagać ponownego przydziału.
Jak String
, który realizuje Deref<Target = str>
, CString
realizuje Deref<Target = CStr>
, więc można wywołać metody zdefiniowane na CStr
bezpośrednio na CString
.Jest to ważne, ponieważ metoda as_ptr()
, która zwraca wartość *const c_char
niezbędną do współdziałania C, jest zdefiniowana na CStr
. Możesz wywołać tę metodę bezpośrednio na wartościach CString
, co jest wygodne.
CString
można utworzyć ze wszystkiego, co można przekonwertować na Vec<u8>
. String
, &str
, Vec<u8>
i &[u8]
są poprawnymi argumentami dla funkcji konstruktora, CString::new()
. Naturalnie, jeśli przekażesz bajtowy wycinek lub wycinek łańcucha, zostanie utworzona nowa alokacja, a zostanie zużyta Vec<u8>
lub 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),
}
}
Jeśli trzeba przenieść własność CString
do kodu C, można zadzwonić CString.html::into_raw
. Następnie musisz odzyskać wskaźnik i uwolnić go w Rust; Przydzielanie Rust jest mało prawdopodobne, aby było to to samo, co program przydzielający używany przez malloc
i free
. Wszystko, co musisz zrobić, to zadzwonić pod numer CString::from_raw
, a następnie zezwolić na normalne upuszczenie łańcucha.
Świetna odpowiedź, to mi pomogło. Czy nie ma bezpieczeństwa w czasie życia cstr, gdy istnieje połączenie z językiem GC, jak C#? – scape
@scape tak, oczywiście, to robi. Powiedziałbym, że jest to jeszcze ważniejsze, ponieważ odśmiecanie może zostać uruchomione w dowolnym momencie, zwłaszcza jeśli jest współbieżne. Jeśli nie dbasz o to, aby sznur po stronie GC został ukorzeniony gdzieś, możesz nagle uzyskać dostęp do uwolnionego fragmentu pamięci po stronie Rdzy. –