Nim backend integration guide describes how to call a Nim function from C.
Example function:
proc fib(a: cint): cint {.exportc.} =
if a <= 2:
result = 1
else:
result = fib(a - 1) + fib(a - 2)
The procedure requires that the Nim compiler is instructed not to create a main
function, avoid linking and creating a header file to FFI from:
$ nim c --noMain --noLinking --header:fib.h fib.nim
To be able to use that function, the C main has to invoke a function called NimMain()
as below:
#include "fib.h"
#include <stdio.h>
int main(void)
{
NimMain();
for (int f = 0; f < 10; f++)
printf("Fib of %d is %d\n", f, fib(f));
return 0;
}
The previously mentioned generated header file is placed in the nimcache
directory. The C compiler has to be instructed to compile all the files under the generated nimcache
sub-directory, nimbase.h
and main.c
:
$ gcc -o m -I$HOME/.cache/nim/fib_d -Ipath/to/nim/lib $HOME/.cache/nim/fib_d/*.c maths.c
How can I instruct the rust compiler to look for those translation units under nimcache
?
In a Rust project, one can have build scripts to compile and link third-party non-Rust code. Combined with cc
crate to make invoke C/C++ compiler easier, this is rather fun.
The project layout:
├── build.rs
├── Cargo.toml
└── src
├── fib.nim
└── main.rs
The build.rs
itself:
use std::io::{self, Write};
use std::process::Command;
fn main() {
let output = Command::new("nim")
.arg("c")
.arg("--noMain")
.arg("--noLinking")
.arg("--nimcache:nimcache")
.arg("src/fib.nim")
.output()
.expect("Failed to invoke nim compiler");
if !output.status.success() {
let msg = String::from_utf8_lossy(output.stderr.as_slice());
let _ = writeln!(io::stderr(), "\nerror occurred: {}\n", msg);
std::process::exit(1);
}
cc::Build::new()
.include("/usr/lib/nim")
.warnings(false)
.file("nimcache/fib.nim.c")
.file("nimcache/stdlib_system.nim.c")
.compile("fib_nim");
}
Notice here there are several platform-dependent bits, mainly the Nim headers location. And the Nim compiler is also told to put intermediate files into a directory called nimcache
inside the project root, instead of the default one under user's home directory.
The Cargo.toml
file:
[package]
name = "nim-ffi"
version = "0.1.0"
authors = ["rustacean"]
edition = "2018"
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
And finally the main Rust source file:
use libc::c_int;
extern "C" {
fn NimMain();
fn fib(_: c_int) -> c_int;
}
fn main() {
// initialize nim gc memory, types and stack
unsafe {
NimMain();
}
let res = unsafe { fib(20) };
println!("Nim fib(20) is: {}", res);
}
It builds and runs successfully:
$ cargo run
Nim fib(20) is: 6765
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With