Skip to content

Commit

Permalink
Auto merge of #25777 - shepmaster:cstring-return-to-c, r=alexcrichton
Browse files Browse the repository at this point in the history
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)
```
  • Loading branch information
bors committed Jun 10, 2015
2 parents db0c1cb + e20a6db commit 01ab4f7
Showing 1 changed file with 41 additions and 4 deletions.
45 changes: 41 additions & 4 deletions src/libstd/ffi/c_str.rs
Expand Up @@ -10,7 +10,9 @@

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

use borrow::Cow;
use borrow::{Cow, ToOwned};
use boxed::{self, Box};
use clone::Clone;
use convert::{Into, From};
use cmp::{PartialEq, Eq, PartialOrd, Ord, Ordering};
use error::Error;
Expand Down Expand Up @@ -61,10 +63,10 @@ use vec::Vec;
/// }
/// # }
/// ```
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[derive(PartialEq, PartialOrd, Eq, Ord, Hash)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct CString {
inner: Vec<u8>,
inner: Box<[u8]>,
}

/// Representation of a borrowed C string.
Expand Down Expand Up @@ -197,7 +199,35 @@ impl CString {
#[stable(feature = "rust1", since = "1.0.0")]
pub unsafe fn from_vec_unchecked(mut v: Vec<u8>) -> CString {
v.push(0);
CString { inner: v }
CString { inner: v.into_boxed_slice() }
}

/// Retakes ownership of a CString that was transferred to C.
///
/// The only appropriate argument is a pointer obtained by calling
/// `into_ptr`. The length of the string will be recalculated
/// using the pointer.
#[unstable(feature = "cstr_memory", reason = "recently added")]
pub unsafe fn from_ptr(ptr: *const libc::c_char) -> CString {
let len = libc::strlen(ptr) + 1; // Including the NUL byte
let slice = slice::from_raw_parts(ptr, len as usize);
CString { inner: mem::transmute(slice) }
}

/// Transfers ownership of the string to a C caller.
///
/// The pointer must be returned to Rust and reconstituted using
/// `from_ptr` to be properly deallocated. Specifically, one
/// should *not* use the standard C `free` function to deallocate
/// this string.
///
/// Failure to call `from_ptr` will lead to a memory leak.
#[unstable(feature = "cstr_memory", reason = "recently added")]
pub fn into_ptr(self) -> *const libc::c_char {
// It is important that the bytes be sized to fit - we need
// the capacity to be determinable from the string length, and
// shrinking to fit is the only way to be sure.
boxed::into_raw(self.inner) as *const libc::c_char
}

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

#[stable(feature = "rust1", since = "1.0.0")]
impl Clone for CString {
fn clone(&self) -> Self {
CString { inner: self.inner.to_owned().into_boxed_slice() }
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl Deref for CString {
type Target = CStr;
Expand Down

0 comments on commit 01ab4f7

Please sign in to comment.