diff options
| author | bors <bors@rust-lang.org> | 2015-06-10 22:37:38 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2015-06-10 22:37:38 +0000 |
| commit | 01ab4f761c09830bdd77726f5ae2351e9e3432c4 (patch) | |
| tree | 4974ad2f22af5f67ed695d20f9848d28e4561cf5 /src/libstd | |
| parent | db0c1cb13c9a1e68c09e2074b6d3a7b38122fb76 (diff) | |
| parent | e20a6dbeed095427e5d5487844f65e7eb1599651 (diff) | |
| download | rust-01ab4f761c09830bdd77726f5ae2351e9e3432c4.tar.gz rust-01ab4f761c09830bdd77726f5ae2351e9e3432c4.zip | |
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)
```
Diffstat (limited to 'src/libstd')
| -rw-r--r-- | src/libstd/ffi/c_str.rs | 45 |
1 files changed, 41 insertions, 4 deletions
diff --git a/src/libstd/ffi/c_str.rs b/src/libstd/ffi/c_str.rs index 8c066b3dc2e..433bb335a80 100644 --- a/src/libstd/ffi/c_str.rs +++ b/src/libstd/ffi/c_str.rs @@ -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; @@ -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. @@ -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. @@ -218,6 +248,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; |
