rust-lang / book

The Rust Programming Language
https://doc.rust-lang.org/book/
Other
15.17k stars 3.41k forks source link

Discussion on section 18: FFI #23

Closed steveklabnik closed 8 years ago

andruhon commented 8 years ago

I believe that better practical example of "Calling Rust code from C" shoud be provided. Especially returning dynamic string, because returning static string is not really practical.

I end up with two approaches but I doubt that any of them is somewhat valid

1) Allocate memory in C with malloc and write result in Rust

#[no_mangle]
pub extern "C" fn rs_string_in_string_out(s_raw: *const c_char, out: *mut c_char) -> c_int {
    // out of some size is allocated in C
    // take string from the input C string
    if s_raw.is_null() { return 0; }

    let c_str: &CStr = unsafe { CStr::from_ptr(s_raw) };
    let buf: &[u8] = c_str.to_bytes();
    let str_slice: &str = std::str::from_utf8(buf).unwrap();
    let str_buf: String = str_slice.to_owned();

    //produce a new string
    let result = String::from(str_buf + " append from Rust");
    let len = result.len();

    //create C string for output
    let c_result = CString::new(result);

    //write string into out pointer passed by C++ addon
    unsafe{ std::ptr::copy(c_result.unwrap().as_ptr(), out, len); };

    // return result length
    return len as c_int;
}

2) Return string and leak it to prevent from being dropped by rust, free by consumer

#[no_mangle]
pub extern "C" fn rs_drop(ptr: *const c_void) {
  mem::drop(ptr);
}

#[no_mangle]
pub extern "C" fn rs_do_something_with_string_1(s_raw: *const c_char) -> *mut c_char {
    // take string from the input C string
    if s_raw.is_null() { panic!(); }

    let c_str: &CStr = unsafe { CStr::from_ptr(s_raw) };
    let buf: &[u8] = c_str.to_bytes();
    let str_slice: &str = std::str::from_utf8(buf).unwrap();
    let str_buf: String = str_slice.to_owned();

    //produce a new string
    let result = String::from(str_buf + " append from Rust");
    let len = result.len();

    //create C string for output
    let c_string = CString::new(result).unwrap();
    let ret: *mut c_char = unsafe {mem::transmute(c_string.as_ptr())};
    mem::forget(c_string); // Leak to prevent deallocation by Rust
    ret
}

//call rs_do_something_with_string_1 and use string and then rs_drop from C/C++
//Or maybe just call free()?
//C:
  //char *from_rust = rs_do_something_with_string_1(cstr);
  //Use string...
  //free(from_rust); //??
  //rs_drop(from_rust); //??
steveklabnik commented 8 years ago

Thanks for the suggestion! I have no idea what this chapter will contain yet, but do think that both ways is important.

andruhon commented 8 years ago

Thanks, Steve! From my point of view this chapter is very important for popularization of the language. We can't write everything in Rust right now, but if we can easily embed it - it is good to go.

p.s. I think that libsys::malloc and then writing bytes into could, potentially, be used similarly to the example №2 above.

andruhon commented 8 years ago

I, probably, did not read existing documentaion properly, but it states that libraries use alloc_system by default, which means that we're free to use free on C/C++ side, so no need in rs_drop mentioned above. It is also possible to add #![feature(alloc_system)] to feel a bit safer.