Search code examples
rustffi

How can I fill an uninitialized Rust vector using a C function?


I'm trying to call a C function that fills in a vector from Rust. Here is a complete minimal working example:

Cargo.toml

[package]
name = "so"
version = "0.1.0"
edition = "2021"

[build-dependencies]
cc = "1.0.72"

build.rs

fn main() {
    cc::Build::new().file("src/library.c").compile("library");
}

src/library.c

void get_ui_array(unsigned long *out, long len) {
  long i;
  for (i = 0; i < len; i++) {
    out[i] = 42;
  }
}

src/main.rs

use std::os::raw::{c_long, c_ulong};

extern "C" {
    pub fn get_ui_array(out: *mut c_ulong, len: c_long);
}

fn get_ui_vector(len: c_long) -> Vec<c_ulong> {
    let mut out = Vec::<c_ulong>::with_capacity(len as usize);
    unsafe {
        get_ui_array(out.as_mut_ptr(), len);
    }
    out
}

fn main() {
    dbg!(get_ui_vector(12));
}

The code compiles, but the unsigned integers in the output are incorrect and seem to be garbage, so I'm assuming it is a lifetime issue. What am I doing wrong? I also tried using MaybeUninit with as_mut_ptr then using std::slice::from_raw_parts but this has the same issue.


Solution

  • but the unsigned integers in the output are incorrect and seem to be garbage

    If you don't use Vec::set_len, the length of the Vec is still zero, even though you've allocated memory and assigned values. If you print out the Vec, it will be empty, so I wonder how you are seeing any integers in the output.

    That said, using set_len should fix the problem:

    fn get_ui_vector(len: c_long) -> Vec<c_ulong> {
        let mut out = Vec::<c_ulong>::with_capacity(len as usize);
        unsafe {
            get_ui_array(out.as_mut_ptr(), len);
            out.set_len(len as usize); // HERE
        }
        out
    }
    

    I assumed with_capacity was setting the length (I'm still not sure why it doesn't).

    When you set the capacity of the vector, you don't set any values inside the vector. If the values are unset, accessing them would cause undefined behavior.

    If you wanted to define the values, you could use something like Vec::resize.

    See also: