What is the proper way to go from a String to a *const i8?

后端 未结 1 964
臣服心动
臣服心动 2020-12-10 15:28

In my ongoing saga of writing a safe wrapper for the Cassandra C++ driver, my eye now turns towards avoiding memory leaks when calling C functions with signatures like:

相关标签:
1条回答
  • 2020-12-10 16:08

    The Cassandra C API for cass_string_init2 looks like:

    Note: This does not allocate memory. The object wraps the pointer passed into this function.

    CASS_EXPORT CassString
    cass_string_init2(const char* data, cass_size_t length);
    

    That is, it takes a string, and returns an alternate representation of a string. That representation looks like:

    typedef struct CassString_ {
        const char* data;
        cass_size_t length;
    } CassString;
    

    This is where you want to use #[repr(C)] in the Rust code:

    #[repr(C)]
    struct CassStr {
        data: *const c_char,
        length: size_t,
    }
    

    The nicest thing you could do is make strings automatically convert to this struct:

    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(),
            }
        }
    }
    

    And then have your API accept anything that implements AsCassStr. This allows you to have owned variants as well. You may also want to look into PhantomData to allow enforcing lifetimes of the CassStr object.

    Note Normally you want to use CString to avoid strings with interior NUL bytes. However, since the API accepts a length parameter, it's possible that it natively supports them. You'll need to experiment to find out. If not, then you'll need to use CString as shown below.

    Second half of question

    Lets take a look at your function, line-by-line:

    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
    }
    
    1. We create a new CString. This allocates a bit of memory somewhere, verifies that the string has no internal NUL bytes, then copies our string in, byte-for-byte, and adds a trailing zero.
    2. We get a slice that refers to the bytes that we have copied and verified in step 1. Recall that slices are a pointer to data plus a length.
    3. We convert the slice to a pointer, ignoring the length.
    4. We store the pointer in a structure, using that as our return value
    5. The function exits, and all local variables are freed. Note that cstr is a local variable, and so the bytes it is holding are likewise freed. You now have a dangling pointer. Not good!

    You need to ensure that the CString lives for as long as it needs to. Hopefully the function you are calling doesn't keep a reference to it, but there's no easy way to tell from the function signature. You probably want code that looks like:

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

    The benefit here is that you never store the pointer in a variable of your own. Remember that raw pointers do not have lifetimes, so you have to be very careful with them!

    0 讨论(0)
提交回复
热议问题