0
votes

EDIT: It was pointed out that my example isn't complete enough to be useful. My question is resolved, but if you're interested in looking at the complete code you can see it here.

Given the following code:

#[no_mangle]
pub extern "C" fn create_acceptor() -> Acceptor {
    Acceptor {}
}

pub struct Acceptor {}

If I call this from C what does it actually get? Is it a pointer? An id of some sort?

I'm currently using code like the following on the C side:

extern int create_acceptor();

int main(int argc, char **argv) {
        int acceptor = create_acceptor();
        return 0;
}

It seems to work fine. I'm using it like an opaque type that I pass back to Rust like acceptor_doSomething(acceptor).

But I'm confused because it doesn't seem to matter what type I use for acceptor on the C side. void* and int both behave the same. Also, the documentation seems to indicate that I should be using #[repr(C)], but it seems to work without it. Why is that?

When printing the value received on the C side, it's showing very small integers, so I'm guessing it's an id on the Rust side?

1
empty type is not a thing in C, Also return an empty type don't make much sense to me, however void should "work" in most case. But int don't make any sense, rust empty type are empty. - Stargateur
Yeah sorry it's not a great example. I edited in a link to my actual code. Anyway my question has been answered. - anderspitman

1 Answers

3
votes

TL;DR: The example isn't a useful one and doesn't show anything.

what does it actually get? Is it a pointer? An id of some sort?

It's the struct, exactly as defined on the Rust side. It's a pointer if you return a pointer (this example does not) or an id if you return some id (this example does not).

Rust's FFI imposes no overhead or abstraction — what you return is what is returned.

the documentation seems to indicate that I should be using #[repr(C)]

Yes, you should. Without it, your code is most likely undefined behavior. The layout of a Rust struct is not yet guaranteed. Without specifying the representation as C, there's no way for the C code to know which fields are where.

but it seems to work without it

That's because your example struct has no fields and thus no size (which AFAIK isn't even valid in C without non-standard extensions). Rust basically never even needs to look at the data (what data?) so it doesn't matter what you pass back.

When printing the value received on the C side, it's showing very small integers

This is probably just junk on the stack laying around. Literally uninitialized data.

Using a struct with fields without #[repr(C)]

This is bad. Don't do it. String is a struct with non-trivial fields and it is not marked #[repr(C)].

Cargo.toml

[package]
name = "shear"
version = "0.1.0"
authors = ["An Devloper"]

[lib]
crate-type = ["cdylib"]

[dependencies]

src/lib.rs

// Don't do this, `String` isn't #[repr(C)]!
#[no_mangle]
pub extern "C" fn create_example() -> String {
    String::from("hello")
}

// Don't do this, `String` isn't #[repr(C)]!
#[no_mangle]
pub extern "C" fn use_example(e: String) {
    println!("{}", e);
}

main.c

extern int create_example();
extern void use_example(int example);

int main(int argc, char **argv) {
  int example = create_example();
  use_example(example);
}

Execution

$ cargo build
   Compiling shear v0.1.0 (file:///home/ubuntu/shear)
    Finished dev [unoptimized + debuginfo] target(s) in 1.02s
$ gcc -o example main.c -L target/debug/ -lshear
$ LD_LIBRARY_PATH=target/debug/ ./example
Segmentation fault

Please read the The Rust FFI Omnibus for details about how to properly transfer values across the FFI boundary. Disclaimer: I am the primary author.