На Хабре в свое время была статья о том, как вызвать Rust код из Go. Статья неплохая, но довольно сложная для понимания и на самом деле отталкивающая новичков от желания смотреть в сторону обоих языков. Цель этого поста не столько залезть в «кишки» кросс-языковых вызовов, сколько показать насколько просто это можно сделать.

image

Далеко ходить не будем и возмем пример из книги по изучению языка Rust.

Все, что делает этот пример, это запускает 10 потоков, внутри которых инкрементирует переменую 'x' до 5 миллионов и выводит сообщение о завершении потока.

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!");
}


Также нужно отредактировать cargo-файл добавив в него строку
crate-type = [«cdylib»]
, в результате чего будет создана библиотека с возможностью вызова фукции через Foreign Function Interface (FFI).

Стоит заметить, что libembed.dylib — это библиотека на Mac OS, на Linux будет libembed.so, а на Windows — libembed.dll

Спасибо: bingo347


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

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


В целом это все, что вы должны сделать в Rust-библиотеке. Пример из книги описывает это более детально и останаливаться на этом мы не будем.

Компилируем библиотеку командой:
cargo build --release
Теперь наша цель вызвать этот код из Go-приложение. Создаем простое приложение и внутри нашего проекта, добавляем папку lib, в которую копируем файл /target/release/libembed.dylib. Внутри создаем файл с названием функции и описываем его сигнатуру вызова.

lib/process.h
void process();


Внутри Go-файл добаляем вот такие директивы и наш main.go будет выглядеть вот так

package main

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

func main() {
	C.process()
}


Собираем проект командой
go build -ldflags="-r /lib" main.go
Обратите внимание на параметр ldflags, в данном случае все, что мы делаем это устанавливаем путь к ELF dynamic linker.

Все. Запускаем программу и получаем вывод.



Также отдельно стоит сказать, что можно передать зачения из Go-программы в Rust-библиотеку. Для этого преобразуем функцию в Rust-библиотеке, чтобы она принимала строковое значение.

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!");
}


Собираем наш проект, снова копируем библиотеку в папку lib, модифицируем файл process.h вот таким образом
void process(char *name);

Передаем строку из Go-приложения (в нашем случае: «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 !!"))
}


Все. Запускаем программу и получаем вывод.