Вызов 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).

    Стоит заметить, что 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 !!"))
    }
    


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

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 11

      0

      Спойлер: Через FFI.

        0
        А есть другой вариант? :))
          0

          Думал, может где-то срезаны углы, чтобы сразу в LLVM-совместимое что-то собрать. Чуда не произошло, увы.

            0
            Будет время, я копну и в эту сторону.
        +1
        По-моему самое сложное в этом вопросе это то, что надо разобраться как работает cgo( который по-факту и не С и не Go), тоже самое наверняка и со стороны раста.
          0
          Это хороший вопрос, который бы хорошо адресовать разработчикам компиляторов :)) В данном случае вызов происходит через FFI, что достаточно неплохо документировано.
            0
            В конкретном случае да, но это легко только пока простыми вещами вроде строк и чисел приходится оперировать. Когда доходит до структур или ссылок, то уже не все так радужно. Тут уже надо садиться и разбираться как это все работает на стыке языков.
          +1

          самая большая проблема примеров с ffi в том, что они либо слишком сложные, либо, как ваш, не несут никакой практической пользы вообще.


          а теперь попробуйте взять этот результат и применить его в своём скрипте на го. то то же.


          покажите как вернуть значение в вызывающий код, расскажите как сделать так чтоб раст не удалил это значение при выходе из контекста, как потом почистить за собой память, есть ли возможность вернуть null и т.д.
          а пример скопипастить на хабр это не интересно.

            0
            Эта статья и не рассматривалась как сложная — на что я и указал в начале.

            покажите как вернуть значение в вызывающий код, расскажите как сделать так чтоб раст не удалил это значение при выходе из контекста, как потом почистить за собой память, есть ли возможность вернуть null и т.д.

            Будет интерес к статья — я расскажу и об этом.

            а пример скопипастить на хабр это не интересно.

            Пример кода на Rust взят с книги, и я показал, как его можно использовать из Go. В книге о Go ничего не говорится, что на самом деле является недостатком.
            +1
            А почему бы не скомпилировать rust библиотеку в static-lib? Насколько помню go прекрасно умеет с ними работать через тот же cgo. Только работать это будет быстрее и соберется все в единый бинарник.

            Ну и так на заметку читающим, libembed.dylib — это мак специфично, на линуксе будет libembed.so, а на винде — libembed.dll
              0
              А почему бы не скомпилировать rust библиотеку в static-lib? Насколько помню go прекрасно умеет с ними работать через тот же cgo. Только работать это будет быстрее и соберется все в единый бинарник.


              В данному случае все также соберется в единый бинарный файл. Будет ли это быстрее, чем static-lib это хороший вопрос и нужно смотреть на уровне ассемблера, как происходит вызов. Ну и опять же — все может зависеть от версии компилятора cgo.

              Ну и так на заметку читающим, libembed.dylib — это мак специфично, на линуксе будет libembed.so, а на винде — libembed.dll

              Спасибо. Хорошее замечания я добавлю в статью.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое