作為新穎、先進的程式語言,Rust的函式庫還沒有C/C++語言的函式庫來得多且完整。在很多時候,我們還是無可避免地必須要去使用現有C/C++程式語言所實作的函式庫來完成我們需要的功能。雖然一般來說,我們還是會比較喜歡用純Rust程式碼來開發程式,確保程式的安全性以及可移植性,但畢竟要把過去每個C/C++程式語言所實作的函式庫,都使用Rust程式語言來改寫,是非常不切實際的事。在開發成本的考量之下,直接讓Rust程式與C/C++程式語言作連結,就變成是一種很常見的折衷作法。



建置腳本(build.rs)

類似於C/C++程式語言程式專案常使用的「configure」,Rust的Cargo程式專案也提供了「建置腳本」的功能,可以讓程式專案在編譯之前,先編譯與執行某個Rust原始碼檔案來設定程式專案的編譯環境。這個「建置腳本」預設會是程式專案根目錄底下的「build.rs」,如果想要更改路徑的話,可以在「Cargo.toml」設定檔案中的「package」區塊,添加「build」設定值。例如:

以上設定檔將作為建置腳本的Rust原始碼檔案路徑設定在程式專案根目錄中的「src」目錄下,檔名為「build.rs」。

建置腳本的撰寫方式可以參考Cargo的官方手冊,網址如下:

https://doc.rust-lang.org/cargo/reference/build-scripts.html

底下先舉一個最簡單的例子,來說明如何使用Rust程式語言呼叫C/C++語言的函式庫。

Rust + C語言的Hello World

首先,使用以下指令建立出名叫「c-hello-world」的Cargo應用程式專案:

cargo new --bin c-hello-world

在程式專案的根目錄中,新增一個「hello-world」目錄,並在該目錄中新增一個「hello.c」檔,檔案內容如下:

在「hello.c」中,我們引入了「stdio.h」標頭檔,並在「greet」函數中使用「printf」函數來印出「Hello world!」字串。

接著,編輯程式專案根目錄中「src」目錄內的「main.rs」檔,檔案內容如下:

在「extern」關鍵字所組成的程式區塊中內,以Rust程式語言定義函數的語法來對應C/C++語言程式的函數,在程式區塊上方使用「#[link(name = "hello-world")]」記號,其中的「hello-world」表示要讓這個程式區塊定義的函數連結到「libhello-world」這個函式庫。當然,我們現在還沒有「libhello-world」這個東西,所以此時的程式專案是無法成功建置的(但是可以通過編譯檢查)。

在Rust程式中呼叫外部函式庫的函數,必須要在不安全的模式下進行,因此「main」函數呼叫「greet」函數時,還另外使用了「unsafe」關鍵字。

再來,在程式專案的根目錄中,新增一個「build.rs」檔案,這個檔案就是我們的建置腳本啦!檔案內容如下:

以上程式,第6行會去取得「OUT_DIR」環境變數的值,表示輸出檔案的儲存路徑。第8行到第10行,其實就如同在程式專案根目錄下,執行了以下指令:

cc hello-world/hello.c -O3 -c -fPIC -o "$OUT_DIR/hello-world.o"

選項「-O3」表示要進行最佳化。選項「-c」表示讓C/C++編譯器不要進行「link」的動作,因為我們要讓它作為函式庫來使用。選項「-fPIC」是要開啟「Position-Independent Code」功能,也是為了要讓程式能夠作為函式庫來使用。選項「-o」則是設定目的檔(object file)要輸出的位置。

程式第12行到第14行,其實就如同在「OUT_DIR」目錄下,執行了以下指令:

ar crus libhello-world.a hello-world.o

「ar crus」指令可以將多個目的檔封裝成單一個「.a」靜態函式庫檔案。在此會把「hello-world.o」封裝成「libhello-world.a」。

程式第16行,設定程式專案在編譯時會去「OUT_DIR」目錄尋找原生函式庫,有點類似C/C++編譯器的「-L」選項。

程式第18行,設定程式專案以靜態的方式來引用「hello-world」這個函式庫(會去尋找libhello-world.a檔案),有點類似在C/C++編譯器加上「-lhello-world」選項。引用了「hello-world」函式庫之後,在Rust程式中就可以使用「#[link(name = "hello-world")]」記號和「extern」關鍵字來對應C/C++的函數。

此時使用「cargo run」來執行程式專案的話,Cargo在編譯程式專案前,會先去編譯並執行專案根目錄下的「build.rs」,所以C語言的「libhello-world.a」靜態函式庫檔案會先被產生出來,然後在編譯程式專案時會引用這個「libhello-world.a」靜態函式庫檔案,將「greet」函數的功能帶進Rust程式中。程式執行的時候就可以看到螢幕印出「Hello world!」字串啦!

Rust型別和C/C++型別的轉換

如果我們需要在Rust程式和C/C++程式間共享記憶體中的變數資料,就必須要透過FFI(Foreign Function Interface,外部函數介面)和「libc」套件來完成。

Crates.io

https://crates.io/crates/libc

Cargo.toml

libc = "*"

稍微改寫一下剛才寫的Hello World程式來舉例吧!

使用「cargo run」來執行程式專案,程式會輸出:

Hello Rust! 7 + 11 = 18