Call Rust functions from Go

At one time, there was an article on Habr on how to call Rust code from Go . The article is not bad, but rather difficult to understand and actually repels beginners from the desire to look towards both languages. The purpose of this post is not so much to get into the guts of cross-language calls, but to show how easy it can be done.

image

We won’t go far and take an example from a book on learning the Rust language.

All that this example does is start 10 threads, within which it increments the variable 'x' to 5 million and displays a message about the end of the stream.

use std::thread;

#[no_mangle]
pub extern "C" fn process() {
    let handles:Vec<_> = (0..10).map(|_|{
        thread::spawn(||{
            let mut x = 0;
            for _ in 0..5_000_000 {
                x += 1
            }
            x
        })
    }).collect();

    for h in handles {
        println!("Thread finished with count={}",
                 h.join().map_err(|_| "Could not join thread!").unwrap());
    }
    println!("Done!");
}


You also need to edit the cargo file by adding a line to it
crate-type = ["cdylib"]
As a result, a library will be created with the ability to call functions via the Foreign Function Interface (FFI) .

It is worth noting that libembed.dylib is a library on Mac OS, on Linux it will be libembed.so, and on Windows it will be libembed.dll

Thanks: bingo347


Cargo.toml
[package]
name = "embed"
version = "0.1.0"

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


In general, this is all you have to do in the Rust library. An example from the book describes this in more detail and we will not dwell on this.

We compile the library with the command:
cargo build --release
Now our goal is to call this code from the Go application. We create a simple application and inside our project, add the lib folder into which we copy the /target/release/libembed.dylib file . Inside, we create a file with the name of the function and describe its call signature.

lib / process.h
void process();


Inside the Go-file we add such directives and our main.go will look like this

package main

/*
#cgo LDFLAGS: -L./lib -lembed
#include "./lib/process.h"
*/
import "C"

func main() {
	C.process()
}


Putting the project together
go build -ldflags = "- r / lib" main.go
Pay attention to the ldflags parameter, in this case all we do is set the path to the ELF dynamic linker.

All. Run the program and get the output.



It is also worth mentioning separately that you can transfer the teachings from the Go-program to the Rust-library. To do this, we transform the function in the Rust library so that it takes a string value.

extern crate libc;
use std::thread;
use std::ffi::CStr;

#[no_mangle]
pub extern "C" fn process(name: *const libc::c_char) {

    let buf_name = unsafe { CStr::from_ptr(name).to_bytes() };
    let str_name = String::from_utf8(buf_name.to_vec()).unwrap();

    let handles:Vec<_> = (0..10).map(|_|{
        thread::spawn(||{
            let mut x = 0;
            for _ in 0..5_000_000 {
                x += 1
            }
            x
        })
    }).collect();

    for h in handles {
        println!("{}:Thread finished with count={}\n",
                 str_name,
                 h.join().map_err(|_| "Could not join thread!\n").unwrap());
    }
    println!("Done!");
}


We assemble our project, copy the library to the lib folder again, modify the process.h file in this way
void process (char * name);

We pass the line from the Go-application (in our case: “Hello from Golang”) .

package main

/*
#cgo LDFLAGS: -L./lib -lembed
#include "./lib/process.h"
*/
import "C"

func main() {
	C.process(C.CString("Hello from Golang !!"))
}


All. Run the program and get the output.


All Articles