15

Here's what I would like to do in C code:

#include <some_lib.h>
int main() {
    some_lib_struct_t x;
    some_lib_func(&x);
}

How do I make use of the library in Rust? Here's what I've got so far:

extern crate libc; // 0.2.51

struct some_lib_struct_t;

#[link(name = "some_lib")]
extern "C" {
    fn some_lib_func(x: *mut some_lib_struct_t);
}

fn main() {
    let mut x: some_lib_struct_t;
    unsafe {
        some_lib_func(&mut x);
    }
}

When compiling I get an error:

error[E0381]: borrow of possibly uninitialized variable: `x`
  --> src/main.rs:13:23
   |
13 |         some_lib_func(&mut x);
   |                       ^^^^^^ use of possibly uninitialized `x`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
EFTH
  • 475
  • 5
  • 14

3 Answers3

10

The safest answer is to initialize the struct yourself:

let mut x: some_lib_struct_t = some_lib_struct_t;
unsafe {
    some_lib_func(&mut x);
}

The closest analog to the C code is to use MaybeUninit

use std::mem::MaybeUninit;

unsafe {
    let mut x = MaybeUninit::uninit();
    some_lib_func(x.as_mut_ptr());
}

Before Rust 1.36, you can use mem::uninitialized:

unsafe {
    let mut x: some_lib_struct_t = std::mem::uninitialized();
    some_lib_func(&mut x);
}

You have to be sure that some_lib_func completely initializes all the members of the struct, otherwise the unsafety will leak outside of the unsafe block.

Speaking of "members of the struct", I can almost guarantee your code won't do what you want. You've defined some_lib_struct_t as having zero size. That means that no stack space will be allocated for it, and a reference to it won't be what your C code is expecting.

You need to mirror the definition of the C struct in Rust so that the appropriate size, padding, and alignment can be allocated. Usually, this means using repr(C).

Many times, C libraries avoid exposing their internal struct representation by always returning a pointer to the opaque type:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I got it working using mem::uninitialized(). But I get a warning from the compiler. I updated the question with more info. – EFTH Apr 11 '17 at 17:10
  • @EFTH I've already addressed that. See the part of my answer talking about "zero size". If you have a *opaque pointer*, then see the 3 linked questions. – Shepmaster Apr 11 '17 at 17:12
  • Note that `std::mem::uninitialized` is *Deprecated since 1.39.0: use `mem::MaybeUninit` instead*. – Lonami Dec 27 '19 at 21:30
3

From the documentation for mem::uninitialized():

Deprecated since 1.39.0: use mem::MaybeUninit instead

The new solution would look like this:

use std::mem::MaybeUninit;

let instance = unsafe {
    let mut x: MaybeUninit<some_lib_struct_t> = MaybeUninit::uninit();
    some_lib_func(x.as_mut_ptr());
    x.assume_init()
}
Lonami
  • 5,945
  • 2
  • 20
  • 38
0

After reading Shepmaster's answer, I looked closer at the header for the library. Just as they said, some_lib_struct_t was just a typedef for a pointer to actual_lib_struct_t. I made the following changes:

extern crate libc;

struct actual_lib_struct_t;
type some_lib_type_t = *mut actual_lib_struct_t;

#[link(name="some_lib")]
extern {
    fn some_lib_func(x: *mut some_lib_type_t);
}

fn main() {
    let mut x: some_lib_type_t;
    unsafe {
        x = std::mem::uninitialized();
        some_lib_func(&mut x);
    }
}

And it works! I do however get the warning found zero-size struct in foreign module, consider adding a member to this struct, #[warn(improper_ctypes)] on by default.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366