GoでRustを呼ぶ。そしてRustでGoを呼ぶ。

# はじめに

Question. GoとRustどちらが優れている？

Answer. どちらも優れてる！

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Indeed!</p>&mdash; Go (@golang) <a href="https://twitter.com/golang/status/1154402973914505221?ref_src=twsrc%5Etfw">July 25, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

この記事ではそれぞれの特徴には言及しません。この記事ではGo側からRustのライブラリを呼ぶ方法と、逆にRust側からGoのライブラリを呼ぶ方法を紹介します。

# FFI (バインディング)

あるプログラミング言語から別のプログラミング言語で定義された関数などを利用するための仕組みを[FFI(Foreign Function Interface)](https://en.wikipedia.org/wiki/Foreign_function_interface)または[バインディング](https://en.wikipedia.org/wiki/Language_binding)と言います。

FFIはその言語のライブラリ・ツールとして実現されていることがほとんどです。Go言語の場合は[cgo](https://github.com/golang/go/wiki/cgo)という実装、Pythonでは[ctypes](https://github.com/python/cpython/tree/master/Lib/ctypes/)という実装がそれぞれの代表的なFFIということになります。

少なくともGoとRustに関してはそれぞれのFFIはC言語のオブジェクトとのみやり取りができ、その部分は処理系(コンパイラとリンカ)が[ABI(Application Binary Interface)](https://en.wikipedia.org/wiki/Application_binary_interface)での[呼出規約](https://en.wikipedia.org/wiki/Calling_convention)に基づいて実施します。[^1]

[^1]: この表現は誤りかもしれません。指摘いただければと思います。

そのため、呼ばれる側の言語は呼ぶ側の言語のFFIが呼べるようにC言語オブジェクトな形にする必要があります。

# GoでRustを呼ぶ

### サンプルプログラムの内容

Rust側のライブラリは入力された文字列の後ろに`(V)[0-0](V)`というカニの顔文字を追加した文字列を返す関数でGo側はその関数を呼びます。

### 構成

以下のようになっています。

```
.
├── Makefile
├── lib
│   ├── rustaceanize
│   │   ├── Cargo.lock
│   │   ├── Cargo.toml
│   │   └── src
│   │       └── lib.rs
│   └── rustaceanize.h
└── main.go
```

### Rust側(ライブラリ)の実装

入力文字列へ文字追加して返す関数が以下の`rustaceanize`関数です。

```rust:lib/rustaceanize/src/lib.rs
extern crate libc;
use std::ffi::{CStr, CString};

#[no_mangle] // no_mangle はRustコンパイラが関数名を変えたり削除しないように必要
pub extern "C" fn rustaceanize(name: *const libc::c_char) -> *const libc::c_char {
    let cstr_name = unsafe { CStr::from_ptr(name) };
    let mut str_name = cstr_name.to_str().unwrap().to_string();
    println!("Rustaceanizing \"{}\"", str_name);
    let r_string: &str = " (V)[0-0](V)";
    str_name.push_str(r_string);
    CString::new(str_name).unwrap().into_raw()
}
```

`*const libc::c_char`型はC言語(GoもRustもC言語型を通したFFIである)が用意した生のcharポインタです。重要なのはこれがC言語側のポインタなのでRustのメモリ管理領域からは外れているという点です。それに対して、`CStr::from_ptr`関数を通して`std::ffi::CStr`というRust側で定義したC言語文字列用型のポインタに変換させています。この処理は`unsafe`スコープ内で処理される必要があります。その理由は[公式ドキュメント](https://doc.rust-lang.org/std/ffi/struct.CStr.html#method.from_ptr)から以下になります。

(筆者訳)

* `*const libc::c_char`の値が有効(validity)である保証がない。
* 返り値のライフタイムが実際のポインタのライフタイムである保証がない。
* `*const libc::c_char`ポインタへのメモリが有効なnul終端文字を含んでいる保証がない。
* `*const libc::c_char`ポインタへのメモリが`CStr`が消える前に変更されることはない、という保証がない。 

関数の最後で`CString::new(str_name).unwrap().into_raw()`のようにしてC言語が扱えるように生のポインタに変換しています。

また、後述のGo言語側でのコンパイルにて関数シンボルが必要になるため以下のようなヘッダーファイルを用意します。

```lib/rustaceanize.h
char* rustaceanize(char *name);
```

### Go側のコード

上述のRustでできるバイナリとビルドするためにGo側では以下の実装になります。

```go:main.go
package main

/*
#cgo LDFLAGS: -L./lib -lrustaceanize
#include <stdlib.h>
#include "./lib/rustaceanize.h"
*/
import "C"

import (
	"fmt"
	"unsafe"
)

func main() {
	s := "I'm a Gopher"

	input := C.CString(s) // Goの管理化のポインタではなくなる。
	defer C.free(unsafe.Pointer(input)) // そのためメモリ解放を実装する必要がある。

	// 以下の場合はinputのメモリはGoの管理化である。
	// このときGo側でGCが働くのでこのプログラムではランタイムエラーが発生する!
	// data := (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
	// input := (*C.char)(unsafe.Pointer(data))

	o := C.rustaceanize(input)

	output := C.GoString(o)
	fmt.Printf("%s\n", output)
}
```

cgoがFFIとして働いています。`LDFLAGS: -L./lib -lrustaceanize`はRustが作るバイナリをビルド時にリンク(go tool link)させるためのオプションです。

`C.CString`と`C.GoString`はそれぞれ文字列をCの構造、Goの構造に変換させるメソッドです。コメントとしても記載していますが、`C.CString`で作られるポインタはGo言語の管理化ではないです。つまりGoのガベージコレクション対象外のポインタになります。そのため上記の例ではC言語のようにメモリ解放を実装しています。

### Build

上述のRustとGoのコードをビルドするために、Rustでは`Cargo.tolm`に下記のように`crate-type = ["cdylib"]`を指定することでよそ行きのバイナリを作成することができます。

```lib/rustaceanize/Cargo.toml
[package]
name = "rustaceanize"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]

[dependencies]
libc = "0.2.2"
```

ビルドするためのMakefileが以下になります。[^2]

[^2]: 注意点として、Mac OSでは`.dylib`という拡張子ですがLinuxでは`.so`になります。

```makefile:Makefile
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))

build:
	cd lib/rustaceanize && cargo build --release
	cp lib/rustaceanize/target/release/librustaceanize.dylib lib/
	echo 'ROOT_DIR is $(ROOT_DIR)'
	go build -ldflags="-r $(ROOT_DIR)lib" main.go
```

Rustが生成したバイナリに対してgo buildで`-ldflags="-r $(ROOT_DIR)lib"`のオプションをつけることでGoビルドでのリンカでGoからRustへの呼び出しを紐付けることができます。

### 実行

実行すると期待どおりに動きます。

```
$ make build
$ ./main
Rustaceanizing "I'm a Gopher"
I'm a Gopher (V)[0-0](V)
```

# RustでGoを呼ぶ

### サンプルプログラムの内容

GoでRustを呼ぶのときと同じ内容です。こっちではGo側はゴーファー君顔文字` ʕ ◔ϖ◔ʔ`を追加して返す関数にします。

### 構成

構成は以下のようになっています。

```
.
├── Cargo.lock
├── Cargo.toml
├── Makefile
├── build.rs
├── golib
│   └── main.go
└── src
    └── main.rs
```

### Go側(ライブラリ)の実装

入力文字列へ文字追加して返す関数が以下の`Gophernize`関数になります。

```go:golib/main.go
package main // ①

import "C"   // ②

// ③
//export Gophernize
func Gophernize(name string) *C.char {
        str := name + " ʕ ◔ϖ◔ʔ"
        return C.CString(str)
}

func main() {} // ④
```

Goが他言語が利用できる関数があるバイナリを作成するためにソースコードでは以下を守る必要があります。[^3]

[^3]: [ここの内容](https://github.com/vladimirvivien/go-cshared-examples)を非常に参考にしました。

* ① `main` package を利用すること。Goコンパイラは`main` packageをビルドしすべての依存モジュール含めてシングルバイナリとして生成される。
* ② コードでは必ず`"C"`をインポートする必要がある。
* ③ 他言語からアクセスさせるために対象の関数上に`//export`のコメントを注釈づけ(annotate)させる。
* ④ 空の`main`関数を宣言しておく必要がある。

### Rust側のコード

Rust側のコードは以下のようになります。

```rust:src/main.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

extern "C" {
    fn Gophernize(name: GoString) -> *const c_char;
}

#[repr(C)]
struct GoString {
    a: *const c_char,
    b: i64,
}

fn main() {
    let s = CString::new("I'm a Rustacean").expect("CString::new failed");
    let ptr = s.as_ptr();
    let input = GoString {
        a: ptr,
        b: s.as_bytes().len() as i64,
    };

    let result = unsafe { Gophernize(input) };
    let c_str = unsafe { CStr::from_ptr(result) };
    let output = c_str.to_str().expect("to_str failed");
    println!("{}", output);
}
```

Go関数への引数となる`GoString`型にある`#[repr(C)]`はC言語のメモリレイアウトにさせるマクロです。`CStr`と`CString`はRust側でCメモリからRustで扱える形で処理するためのもので、C側に渡す場合は`#[repr(C)]`がFFIになるようです。

`Gophernize`は`extern`な外部の関数なので"unsafe function"にあたります。なので呼び出しも`unsafe`スコープで囲う必要があります。

`CStr::from_ptr`はGoでRustを呼ぶときと同様な理由で`unsafe`スコープで囲まれる必要があります。

### Build

Go言語の場合はgo buildにLDFALGオプションを指定してビルド時にリンカへ指示を与えることができましたが、Rustの場合は`build.rs`というファイルにてリンクさせるための情報を記載する必要があります。 [^4]

[^4]: [公式ドキュメント](https://doc.rust-lang.org/cargo/reference/build-scripts.html)

今回の場合は以下のような内容です。

```rust:build.rs
fn main() {
    let path = "./golib";
    let lib = "gophernize";

    println!("cargo:rustc-link-search=native={}", path);
    println!("cargo:rustc-link-lib=static={}", lib);
}
```

`Makefile`の中身は以下のようになっています。

```makefile:Makefile
build:
        cd golib && go build -buildmode=c-archive -o libgophernize.a main.go
        cargo build
```

Go側にて`-buildmode=c-archive`のオプションをつけることがポイントになります。これはRustの場合での`crate-type = ["cdylib"]`と同様によそ行き用のバイナリにするために必要なオプションになります。

### 実行

```
$ make build
$ ./target/debug/call-go-from-rust
I'm a Rustacean ʕ ◔ϖ◔ʔ
```

# おわりに

FFIの部分やリンカについて個人的に色々勉強になりました。これからもGoもRustも使っていこう。

# 参考

* Software Design 2020年12月号 "作品でみせるGoプログラミング"
* https://qiita.com/yugui/items/e71d3d0b3d654a110188
* https://blog.arranfrance.com/post/cgo-sqip-rust/
* https://github.com/vladimirvivien/go-cshared-examples
* https://github.com/mediremi/rust-plus-golang
* https://github.com/arranf/responsive-image-to-hugo-shortcode
* https://github.com/arranf/sqip-ffi
* https://www.altoros.com/blog/golang-internals-part-3-the-linker-object-files-and-relocations/
* https://stackoverflow.com/questions/47074919/how-can-i-call-a-rust-function-from-go-with-a-slice-as-a-parameter
* https://speakerdeck.com/filosottile/calling-rust-from-go-without-cgo-at-gothamgo-2017