CStr::as_mut_ptr for FFI

Hey there, I'm doing some FFI code and noticed &mut CStr does not have a as_mut_ptr method like &mut str does.

What exactly are you trying to do? Any writes to an &mut CStr would have to ensure that the length of the C string doesn't change as &mut CStr is a fat pointer consisting of both a pointer to the string itself and a usize containing the length of the C string. If the FFI code modifies the length, you would get UB.

I'm not writing the length, nor am I giving direct access to &CStr to the C code, I'm just giving a *mut i8 pointer to code I'm 100% sure does not modify the string but did not annotate as such. C is opposite to Rust in the sense that it is mutable by default, so some code out there probably does not modify the string content, but it does not tell that using the type system.

For context, CString has a into_raw that consumes itself and returns a mut pointer. What I'm doing currently, is turning a CString into a Pin<Box<CStr>>, hence why I need to get a *mut i8 from a &mut CStr

If you're certain that the FFI code does not modify the string, then you can .as_ptr() as *mut c_char just fine. If the FFI code will not ever mutate the string, then requiring &mut access is overly restrictive and not a correct wrapping of the call in the first place.

Pin does nothing in this situation. CStr is Unpin, thus Pin<Box<CStr>> is no different from Box<CStr>.

3 Likes

If you're certain that the FFI code does not modify the string, then you can .as_ptr() as *mut c_char just fine.

My point is, it does not make sense to allow CString -> * mut c_char but disallow CString -> Box<CStr> -> *mut c_char. Regardless of my problem at hand, you should be allow to get a *mut c_char from a &mut CStr. At least for Box<CStr>, it should be possible, because it is an owned pointer.

Pin does nothing in this situation. CStr is Unpin, thus Pin<Box<CStr>> is no different from Box<CStr>.

I'm still getting my head around what Pin means. Even if the type is Unpin, shouldn't Pin guarantee it's not moved in memory?

No: Pin in std::pin - Rust

CString -> *mut c_char consumes the CString, so there's nothing that remembers the length. You could technically do the same for Box<CStr> -> *mut c_char, but the reasoning doesn't hold for &mut CStr because you still have the CStr even after turning it into a pointer.

No, Unpin is defined as being implemented by "Types that can be safely moved after being pinned.". It effectively cancels the effect of Pin.

1 Like

CString does have an essentially no-op impl From<Box<CStr, Global>>, so maybe you could just do CString::from(boxed).into_raw()?

Is the possibility of writing a 0-byte within it the only issue with a mutable CStr? That is, could we have a safe &mut CStr -> &mut [NonZeroU8] (which doesn't include the nul-terminator)? Clearly the current API is exclusively geared towards non-mutable uses, as there's no way to use a &mut CStr and the only way to get one is from a Box<CStr>.

3 Likes