Можно долго вести священные войны о языках программирования. Каждый из них сочетает в себе достоинства и недостатки. Всегда найдётся пример, когда один язык проигрывает другому на определённой задаче. Некоторые из них вполне могут сосуществовать рядом в одной программе. В этом посте я расскажу, как связать Go и Rust в одно целое.


Сей пост является ответом на Почему Go и Rust не соперники и Почему Go и Rust не соперники, а чертовы враги.

У rust и go есть много различий. Разный подход к управлению памятью, разные предки, разная цель в конце концов. Их сфера применения пересекается лишь частично. Если rust больше заботится о безопасном управлении памятью, то go старается улучшить читабельность кода.

Но есть у языков и сходства. Оба языка компилируемые. Оба стараются вобрать в себя лучшее из уже проверенных практик. А главное, у go и rust есть общий друг. Один из самых старых языков. Пожалуй самый распространённый и кроссплатформенный. Язык C. И тот и другой имеют инструменты для вызова сишного кода. А значит никто не мешает связать их вместе на этапе линковки и заставить их дёргать функции друг друга.

Начнём с rust


Первое, что следует сделать перед сборкой кода на rust это описать cargo-пакет. Поскольку go может создавать только исполняемые файлы, коду на rust придётся побыть статической библиотекой. Для этого создаём файл Cargo.toml:
[package]
name = "hello"
version = "0.1.0"
autors = ["Lain-dono <lain.dono@gmail.com>"]

[lib]
name = "hello"
path = "lib.rs"
crate-type = ["staticlib"]

[dependencies]
libc = "0.1.8"

Раздел package описывает метаинформацию о пакете, раздел lib описывает что и как собирать, а раздел dependencies управляет зависимостями. Зависимость только от стандартной библиотеки Си. Результат складывается в папку /target/debug или /target/release в зависимости от переданных cargo ключей.
Теперь очередь кода. Для начала импортируем необходимое.
extern crate libc;
use std::ffi::{CStr, CString};

Теперь надо решить, что именно мы будем вызывать из кода на go. Пусть это будет функция с одним строковым параметром:
extern { fn HelloFromGo(name: *const libc::c_char); }

В свою очередь нам надо объявить функцию, которую может подключить и вызвать go. Обратите внимание на #[no_mangle] и extern "C". Первое говорит rust-y, что не следует всячески переиначивать название ф-ии в процессе компиляции, а второе, что следует экспортировать ф-ю в качестве сишной.
#[no_mangle]
pub extern "C" fn hello_from_rust(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();
	println!("go\t: {}", str_name);

Чуть далее вызовем go-функцию.
	let hello = CString::new("Привет :3 Кстати, тут Nim не пробегал?").unwrap();
	unsafe { HelloFromGo(hello.as_ptr()); }
}


Очередь go


Теперь нам следует написать код, вызывающий hello_from_rust и содержащий main-функцию.

Объявляем пакет под названием main и импортируем библиотеку вывода:
package main

import "fmt"

Теперь нам следует импортировать всё, что мы написали на rust. При вызове сборочной утилиты go вызывает в том числе и линковщик. Договоримся, что у нас релизная версия библиотеки, написанной на rust. И поскольку у нас статическая библиотека, следует так же импортировать m и dl.
/*
#cgo LDFLAGS: -L./target/release -lhello -lm -ldl
void hello_from_rust(char *name);
*/
import "C"

Следом надо написать функцию, которая будет вызываться из rust. Комментарий перед ф-ей говорит компилятору go оную импортировать.
//export HelloFromGo
func HelloFromGo(name *C.char) {
	fmt.Println("rust\t:", C.GoString(name))
}

Осталось только написать точку входа в приложение. Здесь вызывается внешняя по отношению к go функция hello_from_rust
func main() {
	C.hello_from_rust(C.CString("Привет Rust!"))
}


Заключение


Вот и всё. Теперь осталось закрепить дружественный союз при помощи Makefile:
build:
	cargo build --release
	go build main.go

clean:
	cargo clean
	rm -f "./main"

Можно спокойно компилировать и запускать. Код можно взять вот здесь.

:wq

Комментарии (24)


  1. CONSTantius
    17.07.2015 14:35
    +35

    Спасибо. Куда полезнее, чем очередной перевод сомнительного срача.


    1. namespace
      17.07.2015 18:19
      -37

      У тебя есть какие-то проблемы с моими срачами переводами?


      1. withkittens
        18.07.2015 01:22
        +9

        Читаю ваши последние комментарии — ох, знатно же у вас подгорает на почве Rust! Не пробовали обратиться к психологу?


        1. voidnugget
          18.07.2015 02:16

          Психологи с таким не помогают, хотя в этой стране, за деньги, — как всегда найдут «универсальную пилюлю» от всех проблем.


  1. Googolplex
    17.07.2015 15:08
    +5

    Спасибо за статью!

    На самом деле, в коммьюнити Rust и Go очень доброжелательные отношения как внутри, так и друг с другом. Фриков, которые обсирают «противоположный» язык, не любят ни там, ни там. Такие статьи, как эта — это очень здорово.

    По коду.

    #[no_mangle]
    pub extern "C" fn hello_from_rust(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();
        println!("go\t: {}", str_name);
    }
    

    Здесь много лишних действий. Вот так (для данного конкретного случая) будет проще:
    use std::str;
    
    pub extern "C" fn hello_from_rust(name: *const libc::c_char) {
        let c_name = unsafe { CStr::from_ptr(name) };
        let str_name = str::from_utf8(c_name.to_bytes()).unwrap();
        println!("go\t: {}", str_name);
    }
    

    В данном случае лишних аллокаций делать совершенно не нужно, и код получается чуть проще.


    1. Semmaz
      17.07.2015 15:38
      +2

      На самом деле, в коммьюнити Rust и Go очень доброжелательные отношения как внутри, так и друг с другом.
      Поддержу! Вот например свежая цитата недели из еженедельника This Week in Rust:
      I think if someone placed the Rust and Go community in a room and asked them to fight, we'd probably just all order pizza and geek out over languages. — Manish Goregaokar


    1. namespace
      17.07.2015 18:22
      -30

      Расскажи мне про любовь гоферов и растофриков, расскажи. Вчера в русскоязычной го-слаке растофрика просто растоптали, а на прошлой неделе, уже в англоязычной, обсирали cargo.

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


      1. Wedmer
        17.07.2015 20:01
        +9

        Похоже, что с вами не так уж и согласны.


        1. namespace
          17.07.2015 20:11
          -29

          Есть такие вещи, о которых обычно не говорят. Я не стесняюсь о них говорить, потому что я молодой, задорный и мне все по балде. Мне глубоко насрать, согласен ты со мной или нет, я оперирую фактами. А факты таковы, что сообщество, а в частности, русскоязычная и англоязычная го-слака, относится к Rust враждебно.

          То как ты эти факты воспринимаешь — это твое дело.


          1. dream_designer
            17.07.2015 23:07
            +16

            Вы может и молодой-задорный, но, что в данном контексте куда важнее, еще и глупый.

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


          1. Wedmer
            17.07.2015 23:44
            +4

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


      1. Zelgadis
        17.07.2015 23:30
        +13

        > Вчера в русскоязычной го-слаке растофрика просто растоптали,

        Я за этим наблюдал. Парень привел весьма славные аргументы, а с вашей (твоей в частности) были только слюни, пена и переходы на личности. Мерзость, а не сообщество у вас там.


      1. voidnugget
        18.07.2015 02:26

        Ключевая фраза в русскоязычной.
        Как показывает практика, люди компенсируются как могут, а за бугром, обычно, это не свойственно — меньше проблем, меньше протестов, и соответственно внутреннего напряжения которое нужно как-то высвободить. Эт я вообще о психологии, если кто не понял…


  1. equand
    17.07.2015 15:30
    -1

    rm -f "./main"


    Shooting in your right foot :D The makefile style


    1. ForNeVeR
      17.07.2015 16:28
      +1

      Так а что тут не так? И как правильно будет?


      1. equand
        17.07.2015 16:36
        +3

        Да я просто представил, если кто-то случайно напишет вместо make — sh на файл из другой папки :/ и удалится main где-то не там.


        1. matiouchkine
          17.07.2015 17:38
          +1

          А если кто-то случайно напишет сразу `rm main`?


  1. namespace
    17.07.2015 18:24
    +1

    Поскольку go может создавать только исполняемые файлы, коду на rust придётся побыть статической библиотекой.

    Это неверно. Go собирается исполняемые файлы только для main-пакетов, для остальных пакетов на выходе получается статическая библиотека. С версии 1.5, Go уже умеет собирать «C archives», тоесть вполно себе можно делать вендорные пакеты.


    1. LeshiyUrban
      18.07.2015 00:09

      c archive это что вроде с расширением .a?


      1. js605451
        18.07.2015 00:15

        С расширением .so тоже умеет, если вопрос в этом.


        1. LeshiyUrban
          18.07.2015 00:45

          Вот теперь жить можно, спасибо. А статьи на эту тему нет?


          1. js605451
            18.07.2015 00:53
            +1

            А вот чтобы прямо статья — нету. Есть некий вот такой документ, есть безумный туториал. Но это всё только в 1.5 будет (в 1.5 beta 2 работает).


          1. Wedmer
            18.07.2015 01:03
            +1

            На эту тему есть issue


    1. Wedmer
      18.07.2015 00:24

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