Why the need for extern "C" keywords?


#1

I’ve been learning how to export functions from a rust library, to be consumed by other applications, written in other languages. I’ve been following the documentation on FFI in rust.

I have it working. I’ve created a library that exports two functions. Both with the #[no_mangle] attribute. I know the #[no_mangle] attribute keeps the function names clear, just by using a tool like nm to see the exported symbols.

I made one function with extern “C” and one without. Both appear to function the same when called from C and python.

I was expecting the extern “C” keywords changed how data for the functions was pushed on the stack. And therefore expecting the functions to behave differently when called from different applications.

So, my question: Why the need for extern “C” keywords? and what does it do exactly?

According to the documentation, The “C” part defines which application binary interface (ABI) the external function uses—the ABI defines how to call the function at the assembly level. The “C” ABI is the most common, and follows the C programming language’s ABI.

So other than conforming to a specification, what does that mean?

lib.rs

#[no_mangle]
pub fn not_extern_c(x : i32, y : i32) -> i32 {
    if x < 0 {
        return y;
    }

    return x;
}

#[no_mangle]
pub extern "C" fn correct_extern_c(x : i32, y : i32) -> i32 {
    if x < 0 {
        return y;
    }

    return x;
}

In C, I call these methods like this:

runlib.c

#include <stdint.h>
#include <stdio.h>

int32_t not_extern_c(int32_t, int32_t);
int32_t correct_extern_c(int32_t, int32_t);

int main() {

    printf("\nshowing results of incorrect and correct use of extern \"C\" functions\n");
    printf("using 'not_extern_c(-1, 4)' output %d\t expected 4\n", not_extern_c(-1, 4));
    printf("using 'not_extern_c(11, 4)' output %d\t expected 11\n", not_extern_c(11, 4));
    printf("using 'correct_extern_c(-1, 4)' output %d\t expected 4\n", correct_extern_c(-1, 4));
    printf("using 'correct_extern_c(11, 4)' output %d\t expected 11\n", correct_extern_c(11, 4));

    return 0;
}

And the output is this:

showing results of incorrect and correct use of extern “C” functions
using 'not_extern_c(-1, 4) output 4 expected 4
using 'not_extern_c(11, 4) output 11 expected 11
using 'correct_extern_c(-1, 4) output 4 expected 4
using 'correct_extern_c(11, 4) output 11 expected 11

Like wise, in python:

runlib.py

import ctypes
myLib1 = ctypes.CDLL("../../lib/target/debug/liblib1.dylib")
print("test functions with/without extern 'c'")
print(str.format("using 'not_extern_c(-1, 4)' output {}\t expected 4", myLib1.not_extern_c(-1, 4)));
print(str.format("using 'not_extern_c(11, 4)' output {}\t expected 11", myLib1.not_extern_c(11, 4)));
print(str.format("using 'correct_extern_c(-1, 4)' output {}\t expected 4", myLib1.correct_extern_c(-1, 4)));
print(str.format("using 'correct_extern_c(11, 4)' output {}\t expected 11", myLib1.correct_extern_c(11, 4)));

And the output is the same:

test functions with/without extern ‘c’
using ‘not_extern_c(-1, 4)’ output 4 expected 4
using ‘not_extern_c(11, 4)’ output 11 expected 11
using ‘correct_extern_c(-1, 4)’ output 4 expected 4
using ‘correct_extern_c(11, 4)’ output 11 expected 11

So I am curious: what does extern “C” do underneath and why (and when) do I need it?
Thnx
Matt


#2

You’re just getting lucky that C ABI and Rust ABI are similar enough in your (simple) case to work, but this is not guaranteed to always be the case, and may give wrong results in more complex scenarios or in later versions of Rust.

Using extern has added benefit of warning you when you use types that are Rust-specific and shouldn’t be used in C.


#3

That’s what I was wondering…

Any ideas what might constitute a complex scenario? I would like to put this into an example that I can show the team so they have a better understanding.

Thnx
Matt


#4

You can take a look at this godbolt example. You’ll see that how an aggregate struct is passed to externed vs non_externed is quite different (and which register they expect to find the argument in).


#5

Perfect. Thank you.