今回はGo、Rustの相互呼び出しを試してみました。これまでCを介していましたが直接呼出しです。
参考)https://qiita.com/momotaro98/items/92e39e214b0e92f454a7
環境)
$ gcc -v
…
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
$ go version
go version go1.20.4 linux/amd64
$ cargo version
cargo 1.69.0 (6e9a83356 2023-04-12)
$ rustc –version
rustc 1.69.0 (84c898d65 2023-04-16)
Call Rust
callrs.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package main /* #cgo LDFLAGS: -L./lib -lhello #include <stdlib.h> #include "./lib/hello.h" */ import "C" import ( "fmt" "unsafe" ) func main() { s := "Go String" input := C.CString(s) defer C.free(unsafe.Pointer(input)) o := C.hello(input) output := C.GoString(o) fmt.Printf("Go main: \"%s\"\n", output) } |
lib/hello/src/lib.rs
1 2 3 4 5 6 7 8 9 10 11 |
extern crate libc; use std::ffi::{CStr, CString}; #[no_mangle] pub extern "C" fn hello(name: *const libc::c_char) -> *const libc::c_char { let cstr = unsafe { CStr::from_ptr(name) }; let s = cstr.to_str().unwrap().to_string(); println!("Rust func hello: \"{}\"", s); let rstr: &str = "Rust String"; CString::new(rstr).unwrap().into_raw() } |
lib/hello.h
1 |
char* hello(char *name); |
lib/hello/Cargo.toml
1 2 3 4 5 6 7 8 9 |
[package] name = "hello" version = "0.1.0" [lib] crate-type = ["cdylib"] [dependencies] libc = "0.2.2" |
Makefile
1 2 3 4 5 6 7 |
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) build: cd lib/hello && cargo build --release cp lib/hello/target/release/libhello.so lib/ echo 'ROOT_DIR is $(ROOT_DIR)' go build -ldflags="-r $(ROOT_DIR)lib" callrs.go |
実行画面
$ ./callrs
Rust func hello: “Go String”
Go main: “Rust String”
Call Go
src/main.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
use std::ffi::{CStr, CString}; use std::os::raw::c_char; extern "C" { fn hello(name: GoString) -> *const c_char; } #[repr(C)] struct GoString { a: *const c_char, b: i64, } fn main() { let s = CString::new("Rust String").expect("CString::new failed"); let ptr = s.as_ptr(); let input = GoString { a: ptr, b: s.as_bytes().len() as i64, }; let result = unsafe { hello(input) }; let c_str = unsafe { CStr::from_ptr(result) }; let output = c_str.to_str().expect("to_str failed"); println!("Rust main: \"{}\"", output); } |
golib/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import "C" import "fmt" //export hello func hello(s string) *C.char { fmt.Printf("Go Func hello: \"%s\" \n", s) str := "Go String" return C.CString(str) } func main() {} |
golib/libhello.h (自動生成)
build.rs
1 2 3 4 5 6 7 |
fn main() { let path = "./golib"; let lib = "hello"; println!("cargo:rustc-link-search=native={}", path); println!("cargo:rustc-link-lib=static={}", lib); } |
Cargo.toml
1 2 3 |
[package] name = "callgo" version = "0.1.0" |
Makefile
1 2 3 4 |
build: cd golib && go build -buildmode=c-archive -o libhello.a main.go cargo build cp target/debug/callgo . |
実行画面
$ ./callgo
Go Func hello: “Rust String”
Rust main: “Go String”
参考サイトに丁寧にかかれていますので、とても参考になりました。
実行場所と文字列の受け渡しをわかりやすくしてみました。
やはりRustは記述が複雑です。それゆえ事例を残しておくことで、あとで参照したいと思っています。