Skip to content

Commit 01ab4f7

Browse files
committed
Auto merge of #25777 - shepmaster:cstring-return-to-c, r=alexcrichton
As far as I was able to determine, it's currently *impossible* to allocate a C NUL-terminated string in Rust and then return it to C (transferring ownership), without leaking memory. There is support for passing the string to C (borrowing). To complicate matters, it's not possible for the C code to just call `free` on the allocated string, due to the different allocators in use. `CString` has no way to recreate itself from a pointer. This commit adds one. This is complicated a bit because Rust `Vec`s want the pointer, size, and capacity. To deal with that, another method to shrink and "leak" the `CString` to a `char *` is also provided. We can then use `strlen` to determine the length of the string, which must match the capacity. **TODO** - [x] Improve documentation - [x] Add stability markers - [x] Convert to `Box<[u8]>` ### Example code With this example code: ```rust #![feature(libc)] #![feature(cstr_to_str)] #![feature(c_str_memory)] extern crate libc; use std::ffi::{CStr,CString}; #[no_mangle] pub extern fn reverse(s: *const libc::c_char) -> *const libc::c_char { let s = unsafe { CStr::from_ptr(s) }; let s2 = s.to_str().unwrap(); let s3: String = s2.chars().rev().collect(); let s4 = CString::new(s3).unwrap(); s4.into_ptr() } #[no_mangle] pub extern fn cleanup(s: *const libc::c_char) { unsafe { CString::from_ptr(s) }; } ``` Compiled using `rustc --crate-type dylib str.rs`, I was able to link against it from C (`gcc -L. -l str str.c -o str`): ```c #include <stdio.h> extern char *reverse(char *); extern void cleanup(char *); int main() { char *s = reverse("Hello, world!"); printf("%s\n", s); cleanup(s); } ``` As well as dynamically link via Ruby: ```ruby require 'fiddle' require 'fiddle/import' module LibSum extend Fiddle::Importer dlload './libstr.dylib' extern 'char* reverse(char *)' extern 'void cleanup(char *)' end s = LibSum.reverse("hello, world!") puts s LibSum.cleanup(s) ```
2 parents db0c1cb + e20a6db commit 01ab4f7

File tree

1 file changed

+41
-4
lines changed

1 file changed

+41
-4
lines changed

src/libstd/ffi/c_str.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
#![unstable(feature = "std_misc")]
1212

13-
use borrow::Cow;
13+
use borrow::{Cow, ToOwned};
14+
use boxed::{self, Box};
15+
use clone::Clone;
1416
use convert::{Into, From};
1517
use cmp::{PartialEq, Eq, PartialOrd, Ord, Ordering};
1618
use error::Error;
@@ -61,10 +63,10 @@ use vec::Vec;
6163
/// }
6264
/// # }
6365
/// ```
64-
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
66+
#[derive(PartialEq, PartialOrd, Eq, Ord, Hash)]
6567
#[stable(feature = "rust1", since = "1.0.0")]
6668
pub struct CString {
67-
inner: Vec<u8>,
69+
inner: Box<[u8]>,
6870
}
6971

7072
/// Representation of a borrowed C string.
@@ -197,7 +199,35 @@ impl CString {
197199
#[stable(feature = "rust1", since = "1.0.0")]
198200
pub unsafe fn from_vec_unchecked(mut v: Vec<u8>) -> CString {
199201
v.push(0);
200-
CString { inner: v }
202+
CString { inner: v.into_boxed_slice() }
203+
}
204+
205+
/// Retakes ownership of a CString that was transferred to C.
206+
///
207+
/// The only appropriate argument is a pointer obtained by calling
208+
/// `into_ptr`. The length of the string will be recalculated
209+
/// using the pointer.
210+
#[unstable(feature = "cstr_memory", reason = "recently added")]
211+
pub unsafe fn from_ptr(ptr: *const libc::c_char) -> CString {
212+
let len = libc::strlen(ptr) + 1; // Including the NUL byte
213+
let slice = slice::from_raw_parts(ptr, len as usize);
214+
CString { inner: mem::transmute(slice) }
215+
}
216+
217+
/// Transfers ownership of the string to a C caller.
218+
///
219+
/// The pointer must be returned to Rust and reconstituted using
220+
/// `from_ptr` to be properly deallocated. Specifically, one
221+
/// should *not* use the standard C `free` function to deallocate
222+
/// this string.
223+
///
224+
/// Failure to call `from_ptr` will lead to a memory leak.
225+
#[unstable(feature = "cstr_memory", reason = "recently added")]
226+
pub fn into_ptr(self) -> *const libc::c_char {
227+
// It is important that the bytes be sized to fit - we need
228+
// the capacity to be determinable from the string length, and
229+
// shrinking to fit is the only way to be sure.
230+
boxed::into_raw(self.inner) as *const libc::c_char
201231
}
202232

203233
/// Returns the contents of this `CString` as a slice of bytes.
@@ -217,6 +247,13 @@ impl CString {
217247
}
218248
}
219249

250+
#[stable(feature = "rust1", since = "1.0.0")]
251+
impl Clone for CString {
252+
fn clone(&self) -> Self {
253+
CString { inner: self.inner.to_owned().into_boxed_slice() }
254+
}
255+
}
256+
220257
#[stable(feature = "rust1", since = "1.0.0")]
221258
impl Deref for CString {
222259
type Target = CStr;

0 commit comments

Comments
 (0)