Вызов Rust функции из Go

от автора

На Хабре в свое время была статья о том, как вызвать 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).

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

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

ссылка на оригинал статьи https://habr.com/ru/post/496192/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *