从String到*const i8的正确方法是什么?

Tup*_*per 8 ffi rust

在我为Cassandra C++驱动程序编写安全包装器的持续传奇中,当我使用以下签名调用C函数时,我的眼睛现在转向避免内存泄漏:

cass_string_init2(const char* data, cass_size_t length);
Run Code Online (Sandbox Code Playgroud)

要么

cass_string_init(const char* null_terminated);
Run Code Online (Sandbox Code Playgroud)

我尝试了一些名义上有效的方法,并产生了正确的结果,但我还没有找到一种方法来正确管理这些数据的生命周期.以下是两种示例方法.

pub fn str_to_ref(mystr:&str) -> *const i8 {unsafe{
    let cstr = CString::from_slice(mystr.as_bytes());
    cstr.as_slice().as_ptr()
}}
Run Code Online (Sandbox Code Playgroud)

pub fn str_to_ref(mystr: &str) -> *const i8 {
    let l = mystr.as_bytes();
    unsafe {
        let b = alloc::heap::allocate(mystr.len()+1, 8);
        let s = slice::from_raw_parts_mut(b, mystr.len()+1);
        slice::bytes::copy_memory(s, l);
        s[mystr.len()] = 0;
        return b as *const i8;
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个做无效的内存访问,如

==26355==  Address 0x782d140 is 0 bytes inside a block of size 320 free'd
==26355==    at 0x1361A8: je_valgrind_freelike_block (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355==    by 0x11272D: heap::imp::deallocate::h7b540039fbffea4dPha (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355==    by 0x112679: heap::deallocate::h3897fed87b942253tba (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355==    by 0x112627: vec::dealloc::h7978768019700822177 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355==    by 0x112074: vec::Vec$LT$T$GT$.Drop::drop::h239007174869221309 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355==    by 0x111F9D: collections..vec..Vec$LT$i8$GT$::glue_drop.5732::h978a83960ecb86a4 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355==    by 0x111F6D: std..ffi..c_str..CString::glue_drop.5729::h953a595760f34a9d (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355==    by 0x112903: cql_ffi::helpers::str_to_ref::hef3994fa55168b90bqd (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
=
Run Code Online (Sandbox Code Playgroud)

而第二个不知道何时释放其内存,导致:

==29782== 8 bytes in 1 blocks are definitely lost in loss record 1 of 115
==29782==    at 0x12A5B2: je_mallocx (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782==    by 0x1142D5: heap::imp::allocate::h3fa8a1c097e9ea53Tfa (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782==    by 0x114221: heap::allocate::h18d191ce51ab2236gaa (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782==    by 0x112874: cql_ffi::helpers::str_to_ref::h5b60f207d1e31841bqd (helpers.rs:25)
Run Code Online (Sandbox Code Playgroud)

使用这两种方法中的任何一种作为起点,或者完全不同的东西,我真的很感激有关正确方法的一些指导.

编辑:

Shep的回答使用cass_string_init和cass_string_init2 完美地解决了我的问题.非常感谢.但是,我仍然不清楚将*const i8 params传递给其他函数,例如:

CASS_EXPORT CassError
cass_cluster_set_contact_points(CassCluster* cluster,
const char* contact_points);
Run Code Online (Sandbox Code Playgroud)

期望传递对以null结尾的字符串的引用.

基于之前为CassStrings工作的方法,以及CString文档,我想出了以下内容:

pub struct ContactPoints(*const c_char);

pub trait AsContactPoints {
    fn as_contact_points(&self) -> ContactPoints;
}

impl AsContactPoints for str {
    fn as_contact_points(&self) -> ContactPoints {
        let cstr = CString::new(self).unwrap();
        let bytes = cstr.as_bytes_with_nul();
        let ptr = bytes.as_ptr();
        ContactPoints(ptr as *const i8)
    }
}
Run Code Online (Sandbox Code Playgroud)

(那里过度让绑定只是为了确保我没有遗漏任何微妙之处)

并且运行正常,但valgrind抱怨:

==22043== Invalid read of size 1
==22043==    at 0x4C2E0E2: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22043==    by 0x4F8AED8: cass_cluster_set_contact_points (in /usr/local/lib/libcassandra.so.1.0.0)
==22043==    by 0x11367A: cql_ffi::cluster::CassCluster::set_contact_points::h575496cbf7644b9e6oa (cluster.rs:76)
Run Code Online (Sandbox Code Playgroud)

She*_*ter 5

cass_string_init2的Cassandra C API如下所示:

CASS_EXPORT CassString
cass_string_init2(const char* data, cass_size_t length);
Run Code Online (Sandbox Code Playgroud)

注意:这不会分配内存.该对象包装传递给此函数的指针.

也就是说,它接受一个字符串,并返回一个字符串的替代表示.该表示看起来像:

typedef struct CassString_ {
    const char* data;
    cass_size_t length;
} CassString;
Run Code Online (Sandbox Code Playgroud)

这是您要#[repr(C)]在Rust代码中使用的位置:

#[repr(C)]
struct CassStr {
    data: *const c_char,
    length: size_t,
}
Run Code Online (Sandbox Code Playgroud)

你能做的最好的事情就是让字符串自动转换为这个结构:

trait AsCassStr {
    fn as_cass_str(&self) -> CassStr;
}

impl AsCassStr for str {
    fn as_cass_str(&self) -> CassStr {
        CassStr {
            data: self.as_bytes(),
            length: self.len(),
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后让你的API接受任何实现的东西AsCassStr.这允许您拥有自己的变体.您可能还希望考虑PhantomData允许强制执行CassStr对象的生命周期.

注意通常,您希望使用CString以避免具有内部NUL字节的字符串.但是,由于API接受长度参数,因此它本身可能支持它们.你需要尝试找出答案.如果没有,那么你需要使用CString如下所示.

问题的下半部分

让我们逐行看看你的功能:

impl AsContactPoints for str {
    fn as_contact_points(&self) -> ContactPoints {
        let cstr = CString::new(self).unwrap(); // 1
        let bytes = cstr.as_bytes_with_nul();   // 2
        let ptr = bytes.as_ptr();               // 3
        ContactPoints(ptr as *const i8)         // 4
    }                                           // 5
}
Run Code Online (Sandbox Code Playgroud)
  1. 我们创造了一个新的CString.这会在某处分配一些内存,验证该字符串没有内部NUL字节,然后逐字节复制我们的字符串,并添加一个尾随零.
  2. 我们得到一个切片,指的是我们在步骤1中复制和验证的字节.回想一下,切片是指向数据加上长度的指针.
  3. 我们将切片转换为指针,忽略长度.
  4. 我们将指针存储在结构中,使用它作为返回值
  5. 函数退出,并释放所有局部变量.注意,这cstr是一个局部变量,因此它所保存的字节同样被释放.你现在有一个悬垂的指针.不好!

你需要确保CString生命的长度,只要它需要.希望您调用的函数不会保留对它的引用,但是从函数签名中找不到简单的方法.您可能希望代码看起来像:

fn my_cass_call(s: &str) {
    let s = CString::new(s).unwrap();
    cass_call(s.as_ptr()) // `s` is still alive here
}
Run Code Online (Sandbox Code Playgroud)

这里的好处是你永远不会将指针存储在你自己的变量中.请记住,原始指针没有生命周期,所以你必须非常小心它们!