Calling C function

I understood from the book, to call a "C" function, we need to:

  • Define the function name and signature in a extern "C" block, as:
extern "C" {
    fn abs(input: i32) -> i32;
}
  • Execute the function using unsafe block, as:
unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
}
// OR
println!("Absolute value of -3 according to C: {}", unsafe { abs(-3) } );

This is if we are calling a C standard function, but what if I want to have my custom C function, for example, I've the below C code:

#include <stdio.h>
int twice(int);

int main(void) {
  printf("Hello World: %d\n", twice(2));
  return 0;
}

int twice(int x) {
  return x * 2;
}

What will be the replacement Rust code:

extern "C" {    // extern block
    fn twice(input: i32) -> i32;    // function name and signature
}

println!("Twice value of 3 according to C: {}", unsafe { twice(3) } );  // execute in unsafe block

But what about the twice function itself, where and how or what shall I do with the:

int twice(int x) {
  return x * 2;
}
1 Like

I believe you'd need to do the following with the C:

extern int twice(int x) {
    return x * 2;
}
int twice(int x) {
    return x * 2;
}

Should work.

In rust you'd do:

extern {
    fn twice(input: i32) -> i32;
}

println!("Twice the value of 3 according to C: {}", unsafe { twice(3) } );

So, you'd compile the C as a library, and link it to the rust at rust compile time.

You don’t need extern on the C function. That’s telling the C compiler the definition of that function is provided externally, and will be resolved by the linker.

The C function can just be what @hyousef wrote initially. That function (or rather its source file, aka translation unit) needs to be compiled (by a C compiler) into, using linux as example, a static lib (eg libfoo.a) or a shared lib (eg libfoo.so). Then that lib needs to be linked to by rustc during its compilation.

1 Like

After long search, I found cc crate, that worked with me in MacOS, not sure about Windows yet.

So, I made build.rs as:

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

And had my twice.c file as:

int twice(int x) {
  return x * 2;
}

The Cargo.toml as:

[package]
name = "rust-c"
version = "0.1.0"
authors = ["Hasan Yousef"]
edition = "2018"

[dependencies]

[build-dependencies]
cc = "1.0"

And the src/main.rs as:

extern "C" {
    fn twice(input: i32) -> i32;
}

fn main() {
    println!("Hello, world!");
    println!("Twice value of 3 according to C: {}", unsafe { twice(3) } );
}

The program tree is:

.
β”œβ”€β”€ Cargo.lock
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ build.rs
β”œβ”€β”€ src
β”‚   └── main.rs
└── twice.c

And execution gave what I want:

2 Likes

Note, your signature is wrong / not portable: if you are using int in C, you need to use ::std::os::raw::c_int or ::libc::c_int.

Imho the best option is to instead use int32_t in your C function.

3 Likes

But why it worked if it is wrong?

Your code works on platforms where int is 32 bits, but not on platforms where int has a different size.

1 Like

Because in practice on most platforms c_int and i32 are the same type.
But there could be some platforms where c_int = i16, for instance.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.