Windows の Rust で go の関数を呼ぶ

GoでRustを呼ぶ。そしてRustでGoを呼ぶ。https://qiita.com/momotaro98/items/92e39e214b0e92f454a7

こちらを参考にさせて頂いたが Windows の場合 go の -buildmode=c-archive で作られる lib が mingw 用なり Windows の Rust が使う MSVC 用と合わないので DLL にして回避した。(具体的には crt の関数がないエラーとセクションの形式が合っていないような警告でリンクできない)

細かい事は省くが x64 の msys2 環境で go も Rust もインストール済みの場合の例です。

main.go

package main

import "C"

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

func main() {}

go のソースコードから "gofuncs.dll" という名前の dll を作る

go build -buildmode=c-shared -o gofuncs.dll ./main.go

dll で公開している関数を記述した def ファイルを作る (TODO: 自動化, dumpbin.exe からできるはず)

gofuncs.def

LIBRARY   gofuncs
EXPORTS
   GoFunc1 @1

lib.exe で DLL からインポートライブラリを作成する

LIBEXE=`cygpath --unix "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC/14.32.31326\bin\Hostx64\x64\lib.exe"`
"$LIBEXE" /def:gofuncs.def /out:gofuncs.lib /machine:x64

これでインポートライブラリ gofuncs.lib (MSVC 形式) が作成される

Rust のソースファイル

use std::ffi::{CStr, CString};
use std::os::raw::c_char;

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

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

fn main() {
	let src_str = CString::new("I'm a Rustacean").expect("CString::new failed");
	let ptr = src_str.as_ptr();
	let param = GoString {
		ptr: ptr,
		len: src_str.as_bytes().len() as i64,
	};
	
	let go_func_result = unsafe { GoFunc1(param) };
	let c_str = unsafe { CStr::from_ptr(go_func_result) };
	let result = c_str.to_str().expect("to_str failed");
	println!("{}", result);
}

cargo でリンクするライブラリを指定

build.rs
fn main() {
	let dir = std::env::var("PWD").unwrap();
	println!("cargo:rustc-link-search={}", std::path::Path::new(&dir).display());
	println!("cargo:rustc-link-lib=gofuncs");
}

これで cargo run すると "I'm a Rustacean ʕ ◔ϖ◔ʔ" と表示される。