3

I am trying to return a struct that can be converted into a Ruby array from an external rust function but when I try to call the structs #to_a method I get a segfault.

use libc::size_t;
#[repr(C)]
pub struct Array {
    len: libc::size_t,
    data: *const libc::c_void,
}

impl Array {
    fn from_vec<T>(mut vec: Vec<T>) -> Array {

        vec.shrink_to_fit();

        let array = Array { data: vec.as_ptr() as *const libc::c_void, len: vec.len() as libc::size_t };

        mem::forget(vec);

        array
    }
}

#[no_mangle]
pub extern fn get_links(url: *const libc::c_char) -> Array {

  // Get links

  let mut urls: Vec<String> = vec![];

  // push strings into urls vec

  // urls => collections::vec::Vec<collections::string::String>

  Array::from_vec(urls)
}
require 'ffi'

module Rust
  extend FFI::Library
  ffi_lib './bin/libembed.dylib'

  class NodesArray < FFI::Struct
      layout :len,    :size_t, # dynamic array layout
             :data,   :pointer #

      def to_a
          self[:data].get_array_of_string(0, self[:len]).compact
      end
  end

  attach_function :get_links, [:string], NodesArray.by_value
end

When I try to use this function in ruby it will return the Fii::NodesArray. I can also get the len and data off of the struct. It is only when I call the #to_a that segfaults.

mpiccolo
  • 662
  • 5
  • 14
  • I'm not familiar with Ruby FFI but... are `len` and `data` supposed to be reversed? It's `data`, `len` in Rust, but `len`, `data` in Ruby. – DK. Jul 02 '15 at 17:10
  • Hmm. Good point. However I do believe I had them in the correct layout at some point and was still segfaulting. I will try switching that and update the question. – mpiccolo Jul 02 '15 at 17:17
  • 1
    You now have two separate definitions of the `Array` struct in your rust source... please post the code that you are using – Adrian Jul 02 '15 at 18:01

2 Answers2

2

The issue, pointed out by Adrian, was that I was pushing strings into the Vec. FFI needs *const libc::c_char, which can be converted from a String.

let mut urls: Vec<*const libc::c_char> = vec![];

urls.push(CString::new(string_var.value.to_string()).unwrap().into_raw());
Community
  • 1
  • 1
mpiccolo
  • 662
  • 5
  • 14
1

It seems like FFI::Pointer#get_array_of_string is bugged (or it just doesn't do what I think it does). Your code works for me if I change this line:

self[:data].get_array_of_string(0, self[:len]).compact

to this:

Array.new(self[:len]) {|i| self[:data].read_pointer[i * FFI::Pointer::SIZE].read_string }
Adrian
  • 14,931
  • 9
  • 45
  • 70
  • 1
    As a side note, you may want to consider using `std::ffi::CString` instead of `String` – Adrian Jul 02 '15 at 18:54
  • Awesome. Thank you Adrian. And yes CString is what I should have been using. – mpiccolo Jul 03 '15 at 02:58
  • One issue though is that I am getting an array of strings only pointing to the first sting in the array. So I am getting an array that looks like this: ["https://www.google.com/webhp?tab=ww", "www.google.com/webhp?tab=ww", "le.com/webhp?tab=ww", "ebhp?tab=ww", "=ww", "\t", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] – mpiccolo Jul 03 '15 at 03:04
  • @mpiccolo I can't imagine why that would be happening. I would recommend posting a new question with a complete example program, the output you are getting, and your expected output. Without that information it is hard to help you. – Adrian Jul 03 '15 at 04:21
  • I posted the fix above. You pointed me in the right direction with reminding me I needed a CString instead of a String. – mpiccolo Jul 03 '15 at 04:29