Можно долго вести священные войны о языках программирования. Каждый из них сочетает в себе достоинства и недостатки. Всегда найдётся пример, когда один язык проигрывает другому на определённой задаче. Некоторые из них вполне могут сосуществовать рядом в одной программе. В этом посте я расскажу, как связать Go и Rust в одно целое.
Сей пост является ответом на Почему Go и Rust не соперники и Почему Go и Rust не соперники, а чертовы враги.
У rust и go есть много различий. Разный подход к управлению памятью, разные предки, разная цель в конце концов. Их сфера применения пересекается лишь частично. Если rust больше заботится о безопасном управлении памятью, то go старается улучшить читабельность кода.
Но есть у языков и сходства. Оба языка компилируемые. Оба стараются вобрать в себя лучшее из уже проверенных практик. А главное, у go и rust есть общий друг. Один из самых старых языков. Пожалуй самый распространённый и кроссплатформенный. Язык C. И тот и другой имеют инструменты для вызова сишного кода. А значит никто не мешает связать их вместе на этапе линковки и заставить их дёргать функции друг друга.
Первое, что следует сделать перед сборкой кода на rust это описать cargo-пакет. Поскольку go может создавать только исполняемые файлы, коду на rust придётся побыть статической библиотекой. Для этого создаём файл Cargo.toml:
Раздел
Теперь очередь кода. Для начала импортируем необходимое.
Теперь надо решить, что именно мы будем вызывать из кода на go. Пусть это будет функция с одним строковым параметром:
В свою очередь нам надо объявить функцию, которую может подключить и вызвать go. Обратите внимание на
Чуть далее вызовем go-функцию.
Теперь нам следует написать код, вызывающий
Объявляем пакет под названием main и импортируем библиотеку вывода:
Теперь нам следует импортировать всё, что мы написали на rust. При вызове сборочной утилиты go вызывает в том числе и линковщик. Договоримся, что у нас релизная версия библиотеки, написанной на rust. И поскольку у нас статическая библиотека, следует так же импортировать
Следом надо написать функцию, которая будет вызываться из rust. Комментарий перед ф-ей говорит компилятору go оную импортировать.
Осталось только написать точку входа в приложение. Здесь вызывается внешняя по отношению к go функция
Вот и всё. Теперь осталось закрепить дружественный союз при помощи Makefile:
Можно спокойно компилировать и запускать. Код можно взять вот здесь.
:wq
Сей пост является ответом на Почему 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