Представьте, что C API предоставляет непрозрачный указатель на некоторые данные и два средства доступа к некоторому полю void set_string(struct foo*, const char*)
и const char* get_string(struct foo*)
, и в этой документации указано что-то вроде:
Строка, возвращаемая
get_string
, действительна до тех пор, пока действителен непрозрачный указатель наfoo
и не выполняется последующий вызовset_string
. В противном случае поведение не определено
Это простой пример заголовка из C, который иллюстрирует проблему.
//foo.h
struct foo;
const char* get_string(struct foo*);
void set_string(struct foo*, const char*);
Мне интересно, можно ли и как заставить средство проверки заимствований ржавчины следить за этой внешней ссылкой и избегать потенциальных UB после создания привязок с помощьюbindgen. Я экспериментировал с
use foo::foo;
use std::marker::PhantomData;
struct Foo<'a> {
pointer: *const foo,
_phantom: PhantomData<&'a CStr>,
}
но, похоже, это тупик.
struct foo*
в C эквивалентен *mut foo
в Rust, а не *const foo
Единственное беспокойство заключается в том, что вы не предоставили документацию, подтверждающую право собственности на const char*
, переданный set_string
. Это просто «ссылка», и она выделяет копию внутри себя? Или функция «претендует на владение» строковыми данными? Если да, то как это освобождает его? Будем надеяться, что в большей части документации об этом говорится, поскольку это повлияет на то, как именно обеспечить этот безопасный интерфейс, но это в значительной степени имеет отношение к тому, как обрабатывать время жизни.
Таким образом, документация, по сути, говорит вам, что значение, возвращаемое get_string
, заимствовано из struct foo
, а set_string
мутирует его, поэтому требуется изменяемое заимствование, вы можете сделать это без какого-либо времени жизни в struct Foo
на стороне Rust, просто оберните вызовы set|get_string
в безопасные абстрагирующие методы :
// creating a module to contain the code that needs to be checked for soundness
mod foo_mod {
use std::ffi::{c_char, CString, CStr};
struct foo;
pub struct Foo {
// musn't be `pub`
pointer: *mut foo,
}
impl Foo {
pub fn set_string(&mut self, s: CString) {
extern "C" {
fn set_string(this: *mut foo, s: *const c_char);
}
// SAFETY:
// this is safe assuming:
// - `self.pointer` is always a valid pointer to a `struct foo`
// - `set_string` in C does not deallocate `s` (unless by leveraging the appropriate Rust code to do so)
unsafe { set_string(self.pointer, s.into_raw()) }
}
pub fn get_string(&self) -> &CStr {
extern "C" {
fn get_string(this: *mut foo) -> *const c_char;
}
// SAFETY:
// this is safe assuming:
// - `self.pointer` is always a valid pointer to a `struct foo`
// - `char* get_string()` always returns a valid C string given a valid `struct foo` pointer
unsafe {
let ptr = get_string(self.pointer);
CStr::from_ptr(ptr)
}
}
pub fn new() -> Self {
// this or any constructors must make sure to only create `Foo`s that contain a valid `pointer`
todo!()
}
}
}
Примечание: Foo::set_string
в Rust безопасен согласно изложенным предположениям, но он пропускает переданное CString
каждый раз, когда вы его используете. В производственной среде это вряд ли то, что вам нужно, но правильная работа с этим зависит от того, как Cs set_string
ожидает, что строка будет передана как kmdreko примечания.
Вы можете обернуть
unsafe
вызовыextern
функций C в безопасный API, который вы предоставляете своему коду Rust: пример.