大家好,我是張晉濤。
近期 Rust 社群/團隊有些變動,所以再一次將 Rust 拉到大多數人眼前。
我最近看到很多小夥伴說的話:
Rust 還值得學嗎?社群是不是不穩定呀
Rust 和 Go 哪個好?
Rust 還值得學嗎?
這些問題如果有人來問我,那我的回答是:
小孩子才做選擇,我都要!
當然,關於 Rust 和 Go 的問題也不算新,比如之前的一條推文:
我在本篇中就來介紹下如何用 Go 呼叫 Rust。
當然,這篇中我基本上不會去比較 Go 和 Rust 的功能,或者這種方式的效能之類的,Just for Fun
FFI 和 Binding
FFI (Foreign Function Interface) 翻譯過來叫做外部函式介面(為了比較簡單,下文中都將使用 FFI 指代)。最早來自於 Common Lisp 的規範,這是在 wiki 上寫的,我並沒有去考證。
不過我所使用過的絕大多數語言中都有 FFI 的概念/術語存在,比如:Python、Ruby, Haskell、Go、Rust、LuaJIT 等。
FFI 的作用簡單來說就是允許一種語言去呼叫另一種語言,有時候我們也會用 Binding 來表示類似的能力。
在不同的語言中會有不同的實現,比如在 Go 中的 cgo , Python 中的 ctypes , Haskell 中的 CAPI (之前還有一個 ccall)等。
我個人感覺 Haskell 中用 FFI 相比其他語言要更簡單&方便的多,不過這不是本篇的重點就不展開了。
在本文中,對於 Go 和 Rust 而言,它們的 FFI 需要與 C 語言物件進行通訊,而這部分其實是由作業系統根據 API 中的呼叫約定來完成的。
我們來進入正題。
準備 Rust 示例程式
Rust 的安裝和 Cargo 工具的基本使用,這裡就不介紹了。大家可以去 Rust 的官網進行了解。
用 Cargo 建立專案
我們先準備一個目錄用來放本次示例的程式碼。(我建立的目錄叫做 go-rust
)
然後使用 Rust 的 Cargo 工具建立一個名叫 rustdemo
的專案,這裡由於我增加了 --lib
的選項,使用其內建的 library 模板。
➜ go-rust git:(master) ✗ mkdir lib && cd lib
➜ go-rust git:(master) ✗ cargo new --lib rustdemo
Created library `rustdemo` package
➜ go-rust git:(master) ✗ tree rustdemo
rustdemo
├── Cargo.toml
└── src
└── lib.rs
1 directory, 2 files
準備 Rust 程式碼
extern crate libc;
use std::ffi::{CStr, CString};
#[no_mangle]
pub extern "C" fn rustdemo(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!("Rust get Input: \"{}\"", str_name);
let r_string: &str = " Rust say: Hello Go ";
str_name.push_str(r_string);
CString::new(str_name).unwrap().into_raw()
}
程式碼比較簡單,Rust 暴露出來的函式名叫做 rustdemo
,接收一個外部的引數,並將其列印出來。之後從 Rust 這邊再設定一個字串。
CString::new(str_name).unwrap().into_raw()
被轉換為原始指標,以便之後由 C 語言處理。
編譯 Rust 程式碼
我們需要修改下 Cargo.toml
檔案以便進行編譯。注意,這裡我們增加了 crate-type = ["cdylib"]
和 libc
。
[package]
name = "rustdemo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
libc = "0.2"
然後進行編譯
➜ rustdemo git:(master) ✗ cargo build --release
Compiling rustdemo v0.1.0 (/home/tao/go/src/github.com/tao12345666333/go-rust/lib/rustdemo)
Finished release [optimized] target(s) in 0.22s
檢視生成的檔案,這是一個 .so
檔案(這是因為我在 Linux 環境下,你如果在其他系統環境下會不同)
➜ rustdemo git:(master) ✗ ls target/release/librustdemo.so
target/release/librustdemo.so
準備 Go 程式碼
Go 環境的安裝之類的這裡也不再贅述了,繼續在我們的 go-rust 目錄操作即可。
編寫 main.go
package main
/*
#cgo LDFLAGS: -L./lib -lrustdemo
#include <stdlib.h>
#include "./lib/rustdemo.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
s := "Go say: Hello Rust"
input := C.CString(s)
defer C.free(unsafe.Pointer(input))
o := C.rustdemo(input)
output := C.GoString(o)
fmt.Printf("%s\n", output)
}
在這裡我們使用了 cgo ,在 import "C"
之前的註釋內容是一種特殊的語法,這裡是正常的 C 程式碼,其中需要宣告使用到的標頭檔案之類的。
下面的程式碼很簡單,定義了一個字串,傳遞給 rustdemo 函式,然後列印 C 處理後的字串。
同時,為了能夠讓 Go 程式能正常呼叫 Rust 函式,這裡我們還需要宣告其標頭檔案,在 lib/rustdemo.h
中寫入如下內容:
char* rustdemo(char *name);
編譯程式碼
在 Go 編譯的時候,我們需要開啟 CGO (預設都是開啟的),同時需要連結到 Rust 構建出來的 rustdemo.so
檔案,所以我們將該檔案和它的標頭檔案放到 lib
目錄下。
➜ go-rust git:(master) ✗ cp lib/rustdemo/target/release/librustdemo.so lib
所以完整的目錄結構就是:
➜ go-rust git:(master) ✗ tree -L 2 .
.
├── go.mod
├── lib
│ ├── librustdemo.so
│ ├── rustdemo
│ └── rustdemo.h
└── main.go
2 directories, 5 files
編譯:
➜ go-rust git:(master) ✗ go build -o go-rust -ldflags="-r ./lib" main.go
➜ go-rust git:(master) ✗ ./go-rust
Rust get Input: "Go say: Hello Rust"
Go say: Hello Rust Rust say: Hello Go
可以看到,第一行的輸出是由 Go 傳入了 Rust , 第二行中則是從 Rust 再傳回 Go 的了。符合我們的預期。
總結
本篇介紹瞭如何使用 Go 與 Rust 進行結合,介紹了其前置關於 FFI 相關的知識,後續通過一個小的實踐演示了其完整過程。
感興趣的小夥伴可以自行實踐下。
歡迎訂閱我的文章公眾號【MoeLove】