Чому Go і Rust не вороги, а друзі

Можна довго вести священні війни про мови програмування. Кожен з них поєднує в собі переваги і недоліки. Завжди знайдеться приклад, коли одна мова програє на іншому певної задачі. Деякі з них цілком можуть співіснувати поруч в одній програмі. У цьому пості я розповім, як зв'язати Go і Rust в одне ціле.


Цей пост є відповіддю на Чому Go і Rust не суперники і Чому Go і Rust не суперники, а кляті вороги.

У rust і go є багато відмінностей. Різний підхід до управління пам'яттю, різні предки, різна мета в кінці кінців. Їх сфера застосування перетинається лише частково. Якщо rust більше піклується про безпечне керування пам'яттю, то go намагається покращити читабельність коду.

Але є у мов і подібності. Обидві мови компилируемые. Обидва намагаються увібрати в себе найкраще з уже перевірених практик. А головне, go і rust є спільний друг. Один з найстаріших мов. Мабуть найпоширеніший і багатоплатформовий. Мова C. І той і інший мають інструменти для виклику сишного коду. А значить ніхто не заважає зв'язати їх разом на етапі лінкування і змусити їх смикати функції один одного.

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

[lib]
name = "привіт"
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 "С"
. Перше говорить rust-y, що не слід всіляко переінакшувати назва ф-ії у процесі компіляції, а друге, що слід експортувати ф-ю в якості сишной.
#[no_mangle]
pub extern "С" 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 "С"

Слідом треба написати функцію, яка буде викликатися з 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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.