Rust 的外部函式介面(FFI)是實作與其他程式語言,特別是 C 和 C++,互操作性的關鍵機制。透過 extern 關鍵字,Rust 可以呼叫外部函式,並將 Rust 函式暴露給其他語言。理解 ABI 的重要性以及如何安全地處理指標在跨語言互動中至關重要。此外,Rust 的 FFI 也支援回呼函式,允許在 C/C++ 程式碼中呼叫 Rust 函式,實作更靈活的整合方案。在處理字串、結構體和陣列等複雜資料型別時,需要謹慎管理記憶體分配和釋放,以避免潛在的錯誤和安全風險。
第17章:外部函式介面(Foreign Function Interface)
簡介
在本章中,我們將探討Rust的外部函式介面(FFI),這是將Rust程式碼與其他程式語言整合的關鍵所在。我們將重點介紹extern關鍵字在Rust中的作用,以及它如何幫助Rust與其他語言進行互動。我們的旅程將涵蓋從C或C++呼叫Rust函式、在Rust和C++之間傳遞字串、結構和陣列、處理跨語言環境中的錯誤和異常安全性等主題。此外,我們還將展示如何為Rust函式庫建立C介面,展示Rust在不同軟體生態系統中的雙向適應性。本章最後將以從Python呼叫Rust程式碼的實際操作來作結,展現Rust的通用性和互操作性。
本章涵蓋以下主題
extern關鍵字- 呼叫外部函式
- 從C或C++呼叫Rust函式
- 在Rust和C++之間傳遞字串、結構和陣列
- 錯誤處理和異常安全性
- 為Rust函式庫建立C介面
目標
在本章結束時,我們旨在全面瞭解Rust中的FFI,探索extern關鍵字、呼叫外部函式以及在Rust和C++之間傳遞資料等基本概念。我們將深入研究跨語言整合中的錯誤處理和異常安全性。此外,我們還將學習如何為Rust函式庫建立C介面,以促進與C和其他支援C繫結的語言之間的無縫互操作性。
extern 關鍵字
Rust中的extern關鍵字用於宣告在其他語言(如C或C++)或外部函式庫中實作的函式。對於在其他Rust crate中實作的函式,可以直接透過模組匯入呼叫,而無需使用extern。以下是使用extern宣告一個簡單函式的例子:
// 使用 'extern' 宣告外部函式
extern "C" {
fn external_function(x: i32) -> i32;
}
fn main() {
let result = unsafe {
external_function(10)
};
println!("外部函式的結果:{}", result);
}
在此範例中,extern "C" 指定了應用二進位制介面(ABI),通知Rust使用C ABI來呼叫此函式。這對於與其他語言編寫的函式相容性至關重要。 fn external_function(x: i32) -> i32; 宣告了函式簽名但未提供其實作。實作將在外部解析,無論是在另一個Rust crate還是在外語的程式碼函式庫中。
使用extern與變數允許Rust連結到並存取在Rust外部定義的變數,例如在C程式碼中宣告的變數。下面是一個簡單的演示:
extern {
static EXTERN_VAL: i32;
}
fn main() {
unsafe {
println!("外部值:{}", EXTERN_VAL);
}
}
在上述程式碼片段中,extern用於宣告EXTERN_VAL是在外部定義的,可以從Rust程式碼中存取。 static EXTERN_VAL: i32; 宣告了EXTERN_VAL的存在而未定義其值。實際值將在與外部程式碼連結時解析。但是,請注意,使用extern會引入不安全的程式碼區塊,因為它與Rust在編譯時無法保證安全的專案互動。因此,在呼叫使用extern宣告的函式或存取變數時,需要使用不安全的區塊。
瞭解並使用extern關鍵字是使用Rust中的FFI的基礎,能夠與其他語言和外部函式庫編寫的程式碼無縫整合。
呼叫外部函式
讓我們來探索呼叫在Rust以外的語言中實作的函式的細微差別,這是與外部程式碼函式庫無縫整合的基本技能。我們的旅程將涵蓋基本函式呼叫、管理指標的複雜性,以及將回呼納入Rust生態系統。核心是extern關鍵字,它是促進Rust與外部函式之間無縫互動的樞紐。
基本函式呼叫
要從Rust呼叫外部函式,extern關鍵字開始發揮作用,允許我們宣告函式簽名並指定適當的ABI。讓我們考慮一個實際範例,從Rust呼叫一個簡單的C函式。首先,讓我們建立一個包含定義函式add_numbers的C檔案,如下所示:
// 檔案:extern_example.c
// 函式定義
int add_numbers(int a, int b) {
return a + b;
}
將此C程式碼儲存在名為extern_example.c的檔案中。然後,將C程式碼編譯成分享函式庫。在終端機中使用以下命令:
gcc -c -o extern_example.o extern_example.c
gcc -shared -o libextern_example.so extern_example.o
這會從C程式碼建立一個名為libextern_example.so的分享函式庫。
現在,讓我們從Rust檔案呼叫C函式add_numbers,如下所示:
// 檔案:main.rs
// 宣告外部C函式
extern "C" {
fn add_numbers(a: i32, b: i32) -> i32;
}
fn main() {
// 呼叫外部函式
let result = unsafe {
add_numbers(5, 6)
};
// 列印結果
println!("新增數字結果:{}", result);
}
在專案根目錄下建立一個build.rs檔案。此檔案是Rust中的特殊建置指令碼,允許您在專案建置過程中執行自訂程式碼。它通常用於連結外部函式庫(例如C/C++函式庫或系統函式庫)等任務。
// 檔案:build.rs
fn main() {
// 取得建置指令碼的當前目錄
let current_dir = std::env::current_dir().unwrap();
// 告訴Cargo連結到函式庫
println!("cargo:rustc-link-lib=dylib=extern_example");
// 告訴Cargo在哪裡找到函式庫
println!("cargo:rustc-link-search=native={}/src/", current_dir.display());
}
現在,使用以下命令編譯Rust程式碼,並與分享函式庫連結:
cargo run
成功編譯後,Rust程式呼叫C函式並顯示結果:
新增數字結果:11
確保C和Rust檔案位於同一目錄,或根據檔案位置調整編譯命令。
處理指標
在使用涉及指標的外部函式時,我們需要在不安全的區塊內處理它們。讓我們來檢查一個範例,示範與操縱指標的C函式之間的互動。假設有一個在C檔案中定義的名為modify_values的函式,如下所示:
// 檔案:extern_pointer_example.c
#include <stdio.h>
// 透過指標修改兩個整數值的函式定義
void modify_values(int* x, int* y) {
*x *= 2;
*y += 3;
}
程式碼解析:
第一段 C 程式碼:定義了一個名為
add_numbers的簡單 C 函式,用於計算兩個整數之和。此範例展示瞭如何從 Rust 中呼叫 C 函式。- 作用:展示如何編寫 C 程式碼並將其編譯成分享函式庫,供 Rust 程式呼叫。
- 邏輯:首先編寫 C 程式碼並將其編譯成分享函式庫,接著在 Rust 中宣告該 C 函式的簽名,並使用
unsafe區塊進行呼叫。
第二段 Rust 程式碼:宣告並呼叫了 C 中的
add_numbers函式。- 作用:演示如何在 Rust 中宣告並呼叫 C 函式。
- 邏輯:透過
extern "C"宣告 C 函式的簽名,並使用unsafe區塊進行呼叫,以確保相容性和安全性。
build.rs組態:組態 Cargo 編譯和連結分享函式庫。- 作用:指示 Cargo 如何連結到由 C 程式碼編譯而成的分享函式庫。
- 邏輯:取得當前目錄,並指示 Cargo 連結到名為
extern_example的動態函式庫,同時指定函式庫的位置。
第三段 C 程式碼:定義了一個名為
modify_values的 C 函式,用於修改兩個整數指標的值。- 作用:展示如何在 C 中操作指標,並準備從 Rust 中呼叫。
- 邏輯:該 C 函式接受兩個整數指標作為引數,並對其所指向的值進行修改。
此範例全面展示了 Rust 如何透過 FFI 與 C 程式碼進行互動,包括基本的函式呼叫和涉及指標的操作,同時保證了操作的正確性和安全性。
圖表翻譯:
此圖示描述了 Rust 與 C 語言之間的互動過程,包括編譯、連結及執行階段的操作流程。
- 編寫 C 程式碼並編譯成分享函式庫。
- 在 Rust 中宣告並呼叫 C 函式。
- 使用
build.rs組態 Cargo 以正確連結分享函式庫。 - 編譯並執行 Rust 程式,完成與 C 程式碼的互動。
圖表翻譯:
此圖示展示了 Rust 與 C 語言之間的互動過程。主要步驟包括:
- 編寫並編譯 C 程式碼為分享函式庫。
- 在 Rust 中宣告 C 函式的簽名並進行呼叫。
- 使用
build.rs組態連結過程。 - 編譯並執行 Rust 程式,完成跨語言互動。
Rust 與 C/C++ 的跨語言互動實務
Rust 的 Foreign Function Interface (FFI) 提供了一種機制,能夠與其他程式語言(如 C 和 C++)進行互動。本章將探討 Rust 如何與 C/C++ 程式碼互操作,包括呼叫 C 函式、處理回呼函式、以及在 Rust 和 C/C++ 之間傳遞字串、結構體和陣列等資料型別。
從 Rust 呼叫 C 函式
Rust 可以呼叫 C 函式,但需要使用 extern "C" 關鍵字宣告 C 函式的介面。以下是一個範例,展示如何從 Rust 呼叫一個 C 函式來修改整數的值:
// C code: modify_values.c
void modify_values(int* x, int* y) {
*x = 10;
*y = 20;
}
// Rust code: main.rs
extern "C" {
fn modify_values(x: *mut i32, y: *mut i32);
}
fn main() {
let mut a = 5;
let mut b = 7;
println!("Before modification: a = {}, b = {}", a, b);
unsafe {
modify_values(&mut a, &mut b);
}
println!("After modification: a = {}, b = {}", a, b);
}
內容解密:
- 在 Rust 中使用
extern "C"宣告 C 函式modify_values。 modify_values函式接受兩個*mut i32指標,並修改它們所指向的值。- 在 Rust 的
main函式中,宣告兩個可變整數a和b,並列印它們的初始值。 - 使用
unsafe區塊呼叫modify_values,並傳遞a和b的可變參考。 - 列印修改後的值。
處理回呼函式
Rust 的 FFI 也支援處理回呼函式,能夠將 Rust 的閉包或函式指標傳遞給 C 函式。以下是一個範例:
// C code: extern_callback_example.c
void perform_operation(int a, int b, void (*callback)(int)) {
int result = a + b;
callback(result);
}
// Rust code: main.rs
extern "C" {
fn perform_operation(a: i32, b: i32, callback: extern "C" fn(i32));
}
fn main() {
extern "C" fn callback_function(result: i32) {
println!("Callback result: {}", result);
}
unsafe {
perform_operation(10, 5, callback_function);
}
}
內容解密:
- 在 Rust 中宣告 C 函式
perform_operation,它接受兩個整數和一個回呼函式指標。 - 定義一個 Rust 函式
callback_function,並使用extern "C"使其與 C 相容。 - 在
main函式中,使用unsafe區塊呼叫perform_operation,並傳遞callback_function。 perform_operation計算結果後呼叫回呼函式,將結果傳遞給callback_function。
從 C 呼叫 Rust 函式
要從 C 呼叫 Rust 函式,需要建立一個與 C 相容的介面。以下是一個範例:
// File: lib.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
// File: main.c
#include <stdio.h>
extern int add(int a, int b);
int main() {
int result = add(5, 6);
printf("Result of adding numbers: %d\n", result);
return 0;
}
內容解密:
- 在 Rust 中定義一個函式
add,並使用#[no_mangle]和extern "C"使其與 C 相容。 - 在 C 程式碼中宣告
add函式,並呼叫它。 - 編譯 Rust 程式碼為動態連結函式庫,並將其連結到 C 程式碼。
在 Rust 和 C++ 之間傳遞字串
在 Rust 和 C++ 之間傳遞字串需要小心處理記憶體管理。以下是一個範例:
// File: lib.rs
use std::ffi::CString;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn process_string(input: *const c_char) -> *mut c_char {
let input_str = unsafe { CString::from_raw(input as *mut c_char) };
let original_string = input_str.to_str().unwrap();
let processed_string = original_string.chars().rev().collect::<String>();
CString::new(processed_string).unwrap().into_raw()
}
內容解密:
- 使用
CString將輸入的 C 風格字串轉換為 Rust 字串。 - 處理字串(在此範例中,將字串反轉)。
- 將處理後的字串轉換為
CString,並傳回其原始指標。