24

I'm very new to Rust. How would I return a String from a Rust function that can be used in Python?

Here is my Rust implementation:

use std::ffi::CString;

#[no_mangle]
pub extern fn query() -> CString {
    let s = CString::new("Hello!").unwrap();
    return s;
}

And the Python code that calls it:

from ctypes import cdll, c_char_p

lib = cdll.LoadLibrary("target/release/libtest.so")
result = lib.query()

print(c_char_p(result).value)

I get a segmentation fault when its run.

EDIT: Using Vladimir Matveev's Rust code below I was able to get it to work with the changes to my python code:

from ctypes import *

lib = cdll.LoadLibrary("target/release/libtest.so")
lib.query.restype = c_char_p
result = lib.query()
print cast(result, c_char_p).value
lib.free_query(result)
LeeMobile
  • 3,775
  • 6
  • 31
  • 32
  • Please review http://stackoverflow.com/questions/30440068/segmentation-fault-when-calling-a-rust-lib-with-ruby-ffi and http://stackoverflow.com/questions/30312885/pass-python-list-to-embedded-rust-function/30313295#30313295 and let us know how your question differs. – Shepmaster May 28 '15 at 15:33
  • I've reviewed both those questions, and they do differ. In the first one, the call comes from Ruby and my question is from Python. In the second question, the return value is an integer, which is a simple case. Here, the return is specifically a string value. – LeeMobile May 28 '15 at 16:05
  • 1
    There's absolutely nothing different on the Rust side that should change depending on the language you are calling from. As far as the Rust code is concerned, C is calling it. Every other language is calling what looks like C code to it. – Shepmaster May 28 '15 at 16:06

2 Answers2

16

The most direct version would be this:

use libc::c_char;
use std::ffi::CString;
use std::mem;

#[no_mangle]
pub extern fn query() -> *mut c_char {
    let s = CString::new("Hello!").unwrap();
    s.into_raw()
}

Here we return a pointer to a zero-terminated sequence of chars which can be passed to Python's c_char_p. You can't return just CString because it is Rust structure which is not supposed to be used in C code directly - it wraps Vec<u8> and actually consists of three pointer-sized integers. It is not compatible with C's char* directly. We need to obtain a raw pointer out of it. CString::into_raw() method does this - it consumes the CString by value, "forgets" it so its allocation won't be destroyed, and returns a *mut c_char pointer to the beginning of the array.

However, this way the string will be leaked because we forget its allocation on the Rust side, and it is never going to get freed. I don't know Python's FFI enough, but the most direct way to fix this problem is to create two functions, one for producing the data and one for freeing it. Then you need to free the data from Python side by calling this freeing function:

// above function
#[no_mangle]
pub extern fn query() -> *mut c_char { ... }

#[no_mangle]
pub extern fn free_query(c: *mut c_char) {
    // convert the pointer back to `CString`
    // it will be automatically dropped immediately
    unsafe { CString::from_raw(c); }
}

CString::from_raw() method accepts a *mut c_char pointer and creates a CString instance out of it, computing the length of the underlying zero-terminated string in the process. This operation implies ownership transfer, so the resulting CString value will own the allocation, and when it is dropped, the allocation gets freed. This is exactly what we want.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • I needed to do the first two steps from [here](https://github.com/rust-lang/libc#usage) to get it working: (1) add dependency to rustc in `Cargo.toml` and (2) import it in the rust file where it's used. (Not sure if this is always necessary though, I'm completely new to rust.) – ArneHugo Sep 11 '16 at 17:55
1

The problem here is that you are returning a CString directly, which does not correspond to the representation of a string in C (you can see here the source code of CString).

You should be returning a pointer to the string, by using s.as_ptr(). However, you need to make sure that the string is not deallocated at the end of the function, since that would result in a dangling pointer.

The only solution I can think of is to use forget to let rust forget the variable instead of freeing it. Of course you will need to find a way to free the string later to avoid a memory leak (see Vladimir's answer).

With the changes I mentioned, your Rust code should be the following:

use std::ffi::CString;
use std::mem;

#[no_mangle]
pub extern fn query() -> *const i8 {
    let s = CString::new("Hello!").unwrap();
    let ptr = s.as_ptr();
    mem::forget(s);
    return ptr;
}
aochagavia
  • 5,887
  • 5
  • 34
  • 53