介紹
本教程會(huì)使用snappy壓縮/解壓縮庫來作為一個(gè) Rust 編寫外部語言代碼綁定的介紹。目前 Rust 還不能直接調(diào)用 C++ 庫,不過 snappy 庫包含一個(gè) C 接口(記錄在snappy-c.h中)。
一個(gè)關(guān)于 libc 的說明
很多這些例子使用libc crate,它提供了很多 C 類型的類型定義,還有很多其他東西。如果你正在自己嘗試這些例子,你會(huì)需要在你的Cargo.toml 中添加libc :
[dependencies]
libc = "0.2.0"
并在你的 crate 根文件添加extern crate libc;
調(diào)用外部函數(shù)
下面是一個(gè)最簡(jiǎn)單的調(diào)用其它語言函數(shù)的例子,如果你安裝了snappy的話它將能夠編譯:
# #![feature(libc)]
extern crate libc;
use libc::size_t;
#[link(name = "snappy")]
extern {
fn snappy_max_compressed_length(source_length: size_t) -> size_t;
}
fn main() {
let x = unsafe { snappy_max_compressed_length(100) };
println!("max compressed length of a 100 byte buffer: {}", x);
}
extern 塊是一個(gè)外部庫函數(shù)標(biāo)記的列表,在這里例子中是 C ABI。#[link(...)] 屬性用來指示鏈接器鏈接snappy 庫來解析符號(hào)。
外部函數(shù)被假定為不安全的所以調(diào)用它們需要包裝在unsafe {} 中,用來向編譯器保證大括號(hào)中代碼是安全的。C 庫經(jīng)常提供不是線程安全的接口,并且?guī)缀跛幸灾羔樧鳛閰?shù)的函數(shù)不是對(duì)所有輸入時(shí)有效的,因?yàn)橹羔樋梢允谴箲业模衣阒羔槼隽?Rust 安全內(nèi)存模型的范圍。
當(dāng)聲明外部語言的函數(shù)參數(shù)時(shí),Rust 編譯器不能檢查它是否正確,所以指定正確的類型是保證綁定運(yùn)行時(shí)正常工作的一部分。
extern 塊可以擴(kuò)展以包括整個(gè) snappy API:
# #![feature(libc)]
extern crate libc;
use libc::{c_int, size_t};
#[link(name = "snappy")]
extern {
fn snappy_compress(input: *const u8,
input_length: size_t,
compressed: *mut u8,
compressed_length: *mut size_t) -> c_int;
fn snappy_uncompress(compressed: *const u8,
compressed_length: size_t,
uncompressed: *mut u8,
uncompressed_length: *mut size_t) -> c_int;
fn snappy_max_compressed_length(source_length: size_t) -> size_t;
fn snappy_uncompressed_length(compressed: *const u8,
compressed_length: size_t,
result: *mut size_t) -> c_int;
fn snappy_validate_compressed_buffer(compressed: *const u8,
compressed_length: size_t) -> c_int;
}
# fn main() {}
創(chuàng)建安全接口
原始 C API 需要需要封裝才能提供內(nèi)存安全性和利用像向量這樣的高級(jí)內(nèi)容。一個(gè)庫可以選擇只暴露出安全的,高級(jí)的接口并隱藏不安全的底層細(xì)節(jié)。
包裝用到了緩沖區(qū)的函數(shù)涉及使用slice::raw 模塊來將Rust向量作為內(nèi)存指針來操作。Rust 的 vector 確保是一個(gè)連續(xù)的內(nèi)存塊。它的長(zhǎng)度是當(dāng)前包含的元素個(gè)數(shù),而容量則是分配內(nèi)存的大小。長(zhǎng)度小于或等于容量。
# #![feature(libc)]
# extern crate libc;
# use libc::{c_int, size_t};
# unsafe fn snappy_validate_compressed_buffer(_: *const u8, _: size_t) -> c_int { 0 }
# fn main() {}
pub fn validate_compressed_buffer(src: &[u8]) -> bool {
unsafe {
snappy_validate_compressed_buffer(src.as_ptr(), src.len() as size_t) == 0
}
}
上面的validate_compressed_buffer 封裝使用了一個(gè)unsafe 塊,不過它通過從函數(shù)標(biāo)記匯總?cè)サ?code>unsafe從而保證了對(duì)于所有輸入調(diào)用都是安全的。
snappy_compress 和snappy_uncompress 函數(shù)更復(fù)雜,因?yàn)檩敵鲆彩褂昧吮环峙涞木彌_區(qū)。
snappy_max_compressed_length 函數(shù)可以用來分配一個(gè)所需最大容量的向量來存放壓縮的輸出。接著這個(gè)向量可以作為一個(gè)輸出參數(shù)傳遞給snappy_compress 。另一個(gè)輸出參數(shù)也被傳遞進(jìn)去并設(shè)置了長(zhǎng)度,可以用它來獲取壓縮后的真實(shí)長(zhǎng)度。
# #![feature(libc)]
# extern crate libc;
# use libc::{size_t, c_int};
# unsafe fn snappy_compress(a: *const u8, b: size_t, c: *mut u8,
# d: *mut size_t) -> c_int { 0 }
# unsafe fn snappy_max_compressed_length(a: size_t) -> size_t { a }
# fn main() {}
pub fn compress(src: &[u8]) -> Vec {
unsafe {
let srclen = src.len() as size_t;
let psrc = src.as_ptr();
let mut dstlen = snappy_max_compressed_length(srclen);
let mut dst = Vec::with_capacity(dstlen as usize);
let pdst = dst.as_mut_ptr();
snappy_compress(psrc, srclen, pdst, &mut dstlen);
dst.set_len(dstlen as usize);
dst
}
}
解壓是相似的,因?yàn)?snappy 儲(chǔ)存了未壓縮的大小作為壓縮格式的一部分并且snappy_uncompressed_length 可以取得所需緩沖區(qū)的實(shí)際大小。
# #![feature(libc)]
# extern crate libc;
# use libc::{size_t, c_int};
# unsafe fn snappy_uncompress(compressed: *const u8,
# compressed_length: size_t,
# uncompressed: *mut u8,
# uncompressed_length: *mut size_t) -> c_int { 0 }
# unsafe fn snappy_uncompressed_length(compressed: *const u8,
# compressed_length: size_t,
# result: *mut size_t) -> c_int { 0 }
# fn main() {}
pub fn uncompress(src: &[u8]) -> Option> {
unsafe {
let srclen = src.len() as size_t;
let psrc = src.as_ptr();
let mut dstlen: size_t = 0;
snappy_uncompressed_length(psrc, srclen, &mut dstlen);
let mut dst = Vec::with_capacity(dstlen as usize);
let pdst = dst.as_mut_ptr();
if snappy_uncompress(psrc, srclen, pdst, &mut dstlen) == 0 {
dst.set_len(dstlen as usize);
Some(dst)
} else {
None // SNAPPY_INVALID_INPUT
}
}
}
接下來,我們可以添加一些測(cè)試來展示如何使用他們:
# #![feature(libc)]
# extern crate libc;
# use libc::{c_int, size_t};
# unsafe fn snappy_compress(input: *const u8,
# input_length: size_t,
# compressed: *mut u8,
# compressed_length: *mut size_t)
# -> c_int { 0 }
# unsafe fn snappy_uncompress(compressed: *const u8,
# compressed_length: size_t,
# uncompressed: *mut u8,
# uncompressed_length: *mut size_t)
# -> c_int { 0 }
# unsafe fn snappy_max_compressed_length(source_length: size_t) -> size_t { 0 }
# unsafe fn snappy_uncompressed_length(compressed: *const u8,
# compressed_length: size_t,
# result: *mut size_t)
# -> c_int { 0 }
# unsafe fn snappy_validate_compressed_buffer(compressed: *const u8,
# compressed_length: size_t)
# -> c_int { 0 }
# fn main() { }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid() {
let d = vec![0xde, 0xad, 0xd0, 0x0d];
let c: &[u8] = &compress(&d);
assert!(validate_compressed_buffer(c));
assert!(uncompress(c) == Some(d));
}
#[test]
fn invalid() {
let d = vec![0, 0, 0, 0];
assert!(!validate_compressed_buffer(&d));
assert!(uncompress(&d).is_none());
}
#[test]
fn empty() {
let d = vec![];
assert!(!validate_compressed_buffer(&d));
assert!(uncompress(&d).is_none());
let c = compress(&d);
assert!(validate_compressed_buffer(&c));
assert!(uncompress(&c) == Some(d));
}
}
析構(gòu)函數(shù)
外部庫經(jīng)常把資源的所有權(quán)傳遞給調(diào)用函數(shù)。當(dāng)這發(fā)生時(shí),我們必須使用 Rust 析構(gòu)函數(shù)來提供安全性和確保釋放了這些資源(特別是在恐慌的時(shí)候)。
關(guān)于析構(gòu)函數(shù)的更多細(xì)節(jié),請(qǐng)看Drop trait
在 Rust 函數(shù)中處理 C 回調(diào)(Callbacks from C code to Rust functions)
一些外部庫要求使用回調(diào)來向調(diào)用者反饋它們的當(dāng)前狀態(tài)或者即時(shí)數(shù)據(jù)。可以傳遞在 Rust 中定義的函數(shù)到外部庫中。要求是這個(gè)回調(diào)函數(shù)被標(biāo)記為extern 并使用正確的調(diào)用約定來確保它可以在 C 代碼中被調(diào)用。
接著回調(diào)函數(shù)可以通過一個(gè)C庫的注冊(cè)調(diào)用傳遞并在后面被執(zhí)行。
一個(gè)基礎(chǔ)的例子:
Rust代碼:
extern fn callback(a: i32) {
println!("I'm called from C with value {0}", a);
}
#[link(name = "extlib")]
extern {
fn register_callback(cb: extern fn(i32)) -> i32;
fn trigger_callback();
}
fn main() {
unsafe {
register_callback(callback);
trigger_callback(); // Triggers the callback.
}
}
C 代碼:
typedef void (*rust_callback)(int32_t);
rust_callback cb;
int32_t register_callback(rust_callback callback) {
cb = callback;
return 1;
}
void trigger_callback() {
cb(7); // Will call callback(7) in Rust.
}
這個(gè)例子中 Rust 的main() 會(huì)調(diào)用 C 中的trigger_callback() ,它會(huì)反過來調(diào)用 Rust 中的callback() 。
在 Rust 對(duì)象上使用回調(diào)(Targeting callbacks to Rust objects)
之前的例子展示了一個(gè)全局函數(shù)是如何在C代碼中被調(diào)用的。然而我們經(jīng)常希望回調(diào)是針對(duì)一個(gè)特殊 Rust 對(duì)象的。這個(gè)對(duì)象可能代表對(duì)應(yīng) C 語言中的封裝。
這可以通過向 C 庫傳遞這個(gè)對(duì)象的不安全指針來做到。C 庫則可以根據(jù)這個(gè)這個(gè)通知中的指針來取得 Rust 對(duì)象。這允許回調(diào)不安全的訪問被引用的 Rust 對(duì)象。
Rust 代碼:
#[repr(C)]
struct RustObject {
a: i32,
// Other members...
}
extern "C" fn callback(target: *mut RustObject, a: i32) {
println!("I'm called from C with value {0}", a);
unsafe {
// Update the value in RustObject with the value received from the callback:
(*target).a = a;
}
}
#[link(name = "extlib")]
extern {
fn register_callback(target: *mut RustObject,
cb: extern fn(*mut RustObject, i32)) -> i32;
fn trigger_callback();
}
fn main() {
// Create the object that will be referenced in the callback:
let mut rust_object = Box::new(RustObject { a: 5 });
unsafe {
register_callback(&mut *rust_object, callback);
trigger_callback();
}
}
C 代碼:
typedef void (*rust_callback)(void*, int32_t);
void* cb_target;
rust_callback cb;
int32_t register_callback(void* callback_target, rust_callback callback) {
cb_target = callback_target;
cb = callback;
return 1;
}
void trigger_callback() {
cb(cb_target, 7); // Will call callback(&rustObject, 7) in Rust.
}
異步回調(diào)
在之前給出的例子中回調(diào)在一個(gè)外部 C 庫的函數(shù)調(diào)用后直接就執(zhí)行了。在回調(diào)的執(zhí)行過程中當(dāng)前線程控制權(quán)從 Rust 傳到了 C 又傳到了 Rust,不過最終回調(diào)和和觸發(fā)它的函數(shù)都在一個(gè)線程中執(zhí)行。
當(dāng)外部庫生成了自己的線程并觸發(fā)回調(diào)時(shí)情況就變得復(fù)雜了。在這種情況下回調(diào)中對(duì) Rust 數(shù)據(jù)結(jié)構(gòu)的訪問時(shí)特別不安全的并必須有合適的同步機(jī)制。除了像互斥量這種經(jīng)典同步機(jī)制外,另一種可能就是使用通道(std::sync::mpsc )來從觸發(fā)回調(diào)的 C 線程轉(zhuǎn)發(fā)數(shù)據(jù)到 Rust 線程。
如果一個(gè)異步回調(diào)指定了一個(gè)在 Rust 地址空間的特殊 Rust 對(duì)象,那么在確保在對(duì)應(yīng) Rust 對(duì)象被銷毀后不會(huì)再有回調(diào)被 C 庫觸發(fā)就格外重要了。這一點(diǎn)可以通過在對(duì)象的析構(gòu)函數(shù)中注銷回調(diào)和設(shè)計(jì)庫使其確保在回調(diào)被注銷后不會(huì)再被觸發(fā)來取得。
鏈接
在extern 上的link 屬性提供了基本的構(gòu)建塊來指示rustc 如何連接到原生庫。現(xiàn)在有兩種被接受的鏈接屬性形式:
#[link(name = "foo", kind = "bar")]
在這兩種形式中,foo 是我們鏈接的原生庫的名字,而在第二個(gè)形式中bar 是編譯器要鏈接的原生庫的類型。目前有 3 種已知的原生庫類型:
- 動(dòng)態(tài) -
#[link(name = "readline")]
- 靜態(tài) -
#[link(name = "my_build_dependency", kind = "static")]
- Frameworks -
#[link(name = "CoreFoundation", kind = "framework")]
注意 Frameworks 只支持 OSX 平臺(tái)。
不同kind 的值意味著鏈接過程中不同原生庫的參與方式。從鏈接的角度看,rus t編譯器創(chuàng)建了兩種組件:部分的(rlib/staticlib)和最終的(dylib/binary)。原生動(dòng)態(tài)庫和框架會(huì)從擴(kuò)展到最終組件部分,而靜態(tài)庫則完全不會(huì)擴(kuò)展。
一些關(guān)于這些模型如何使用的例子:
- 一個(gè)原生構(gòu)建依賴。有時(shí)編寫部分Rust代碼時(shí)需要一些C/C代碼,另外使用發(fā)行為庫格式的C/C代碼只是一個(gè)負(fù)擔(dān)。在這種情況下,代碼會(huì)被歸檔為
libfoo.a 然后rust包裝箱可以通過#[link(name = "foo", kind = "static")] 聲明一個(gè)依賴。
不管包裝箱輸出為何種形式,原生靜態(tài)庫將會(huì)包含在輸出中,這意味著分配一個(gè)原生靜態(tài)庫是沒有必要的。
- 一個(gè)正常動(dòng)態(tài)庫依賴。通用系統(tǒng)庫(像
readline )在大量系統(tǒng)上可用,通常你找不到這類庫的靜態(tài)拷貝。當(dāng)這種依賴被添加到包裝箱里時(shí),部分目標(biāo)(比如rlibs)將不會(huì)鏈接這些庫,但是當(dāng)rlib被包含進(jìn)最終目標(biāo)(比如二進(jìn)制文件)時(shí),原生庫將被鏈接。
在OSX上,框架與動(dòng)態(tài)庫有相同的語義。
不安全塊(Unsafe blocks)
一些操作,像解引用不安全的指針或者被標(biāo)記為不安全的函數(shù)只允許在 unsafe 塊中使用。unsafe 塊隔離的不安全性并向編譯器保證不安全代碼不會(huì)泄露到塊之外。
不安全函數(shù),另一方面,將它公布于眾。一個(gè)不安全的函數(shù)這樣寫:
unsafe fn kaboom(ptr: *const i32) -> i32 { *ptr }
這個(gè)函數(shù)只能被從unsafe 塊中或者unsafe 函數(shù)調(diào)用。
訪問外部全局變量(Accessing foreign globals)
外部 API 經(jīng)常導(dǎo)出一個(gè)全局變量來進(jìn)行像記錄全局狀態(tài)這樣的工作。為了訪問這些變量,你可以在extern 塊中用static 關(guān)鍵字聲明它們:
# #![feature(libc)]
extern crate libc;
#[link(name = "readline")]
extern {
static rl_readline_version: libc::c_int;
}
fn main() {
println!("You have readline version {} installed.",
unsafe { rl_readline_version as i32 });
}
另外,你可能想修改外部結(jié)接口提供的全局狀態(tài)。為了做到這一點(diǎn),聲明為mut 這樣我們就可以改變它了。
# #![feature(libc)]
extern crate libc;
use std::ffi::CString;
use std::ptr;
#[link(name = "readline")]
extern {
static mut rl_prompt: *const libc::c_char;
}
fn main() {
let prompt = CString::new("[my-awesome-shell] $").unwrap();
unsafe {
rl_prompt = prompt.as_ptr();
println!("{:?}", rl_prompt);
rl_prompt = ptr::null();
}
}
注意與static mut 變量的所有交互都是不安全的,包括讀或?qū)憽Ec全局可變量打交道需要足夠的注意。
外部調(diào)用約定(Foreign calling conventions)
大部分外部代碼導(dǎo)出為一個(gè) C 的 ABI,并且 Rust 默認(rèn)使用平臺(tái) C 的調(diào)用約定來調(diào)用外部函數(shù)。一些外部函數(shù),尤其是大部分 Windows API,使用其它的調(diào)用約定。Rust 提供了一個(gè)告訴編譯器應(yīng)該用哪種調(diào)用約定的方法:
# #![feature(libc)]
extern crate libc;
#[cfg(all(target_os = "win32", target_arch = "x86"))]
#[link(name = "kernel32")]
#[allow(non_snake_case)]
extern "stdcall" {
fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> libc::c_int;
}
# fn main() { }
這適用于整個(gè)extern 塊。被支持的ABI約束的列表為:
vectorcall 目前隱藏于abi_vectorcall gate 之后并傾向于改變。
列表中大部分 ABI 都是自解釋的,不過system ABI 可能看起來有點(diǎn)奇怪。這個(gè)約束會(huì)選擇任何能和目標(biāo)庫正確交互的 ABI。例如,在x86架構(gòu)上,這意味著會(huì)使用stdcall ABI。然而,在 x86_64 上 windows 使用C 調(diào)用約定,所以C 會(huì)被使用。這意味在我們之前的例子中,我們可以使用extern "system" { ... } 定義一個(gè)適用于所有 windows 系統(tǒng)的塊,而不僅僅是 x86 系統(tǒng)。
外部代碼交互性(Interoperability with foreign code)
只有當(dāng)#[repr(C)] 屬性被用于結(jié)構(gòu)體時(shí)Rust能確保struct 的布局兼容平臺(tái)的 C 的表現(xiàn)。#[repr(C, packed)] 可以用來不對(duì)齊的排列結(jié)構(gòu)體成員。#[repr(C)] 也可以被用于一個(gè)枚舉。
Rust 擁有的裝箱(Box<T> )使用非空指針作為指向他包含的對(duì)象的句柄。然而,它們不應(yīng)該手動(dòng)創(chuàng)建因?yàn)樗鼈冇蓛?nèi)部分分配器托管。引用可以被安全的假設(shè)為直接指向數(shù)據(jù)的非空指針。然而,打破借用檢查和可變性規(guī)則并不能保證安全,所以傾向于只在需要時(shí)使用裸指針(* )因?yàn)榫幾g器不能為它們做更多假設(shè)。
向量和字符串共享同樣基礎(chǔ)的內(nèi)存布局,vec 和str 模塊中可用的功能可以操作 C API。然而,字符串不是\0 結(jié)尾的。如果你需要一個(gè)NUL結(jié)尾的字符串來與 C 交互,你需要使用std::ffi 模塊中的CString 類型。
標(biāo)準(zhǔn)庫中的libc 模塊包含類型別名和 C 標(biāo)準(zhǔn)庫中的函數(shù)定義,Rust 默認(rèn)鏈接libc 和libm 。
可變參數(shù)函數(shù)
在 C 語言中,函數(shù)是“可變參數(shù)的”,這意味著它可以接受可變數(shù)量的參數(shù)。在 Rust 中可以通過在外部函數(shù)聲明中指定... 來實(shí)現(xiàn):
extern {
fn foo(x: i32, ...);
}
fn main() {
unsafe {
foo(10, 20, 30, 40, 50);
}
}
普通的 Rust 不能是可變參數(shù)的:
// This will not compile
fn foo(x: i32, ...) { }
“可空指針優(yōu)化”(The "nullable pointer optimization")
特定的 Rust 類型被定義為永遠(yuǎn)不能為null 。這包括引用(&T ,&mut T ),裝箱(Box<T> ),和函數(shù)指針(extern "abi" fn() )。當(dāng)調(diào)用 C 接口時(shí),可以為null 的指針被廣泛使用,這好像會(huì)需要使用到一些混亂的transmutes 和/或不安全代碼來處理與 Rust 類型間的相互轉(zhuǎn)換。不過,Rust 語言提供了一個(gè)變通方案。
作為一個(gè)特殊的例子,一類只包含兩個(gè) variant 的enum 適合用來進(jìn)行“可空指針優(yōu)化”,其中一個(gè) variant 不包含數(shù)據(jù),另一個(gè)包含一個(gè)上述不可空類型的字段。這意味著不需要額外的空間來進(jìn)行判別,相反,空的 variant 表現(xiàn)為將一個(gè)null 值放入不可空的字段。這也被稱為一種優(yōu)化,不過不同于別讀優(yōu)化它保證適用于合適的類型。
得益于這種可空指針優(yōu)化的最常見的例子是Option<T> ,這里None 對(duì)應(yīng)null 。所以Option<extern "C" fn(c_int) -> c_int> 是一個(gè)用于 C ABI 的可空函數(shù)指針的正確方式(對(duì)應(yīng) C 類型int (*)(int) )。
這是一個(gè)不太自然的例子。假如一些 C 庫擁有一個(gè)注冊(cè)回調(diào)的功能,它在特定情況下被調(diào)用。回調(diào)傳遞了一個(gè)函數(shù)指針和一個(gè)整型并應(yīng)該以這個(gè)整型作為參數(shù)執(zhí)行這個(gè)函數(shù)。所以我們有一些雙向穿越 FFI boundary 的函數(shù)指針。
# #![feature(libc)]
extern crate libc;
use libc::c_int;
# #[cfg(hidden)]
extern "C" {
/// Registers the callback.
fn register(cb: Option c_int>, c_int) -> c_int>);
}
# unsafe fn register(_: Option c_int>,
# c_int) -> c_int>)
# {}
/// This fairly useless function receives a function pointer and an integer
/// from C, and returns the result of calling the function with the integer.
/// In case no function is provided, it squares the integer by default.
extern "C" fn apply(process: Option c_int>, int: c_int) -> c_int {
match process {
Some(f) => f(int),
None => int * int
}
}
fn main() {
unsafe {
register(Some(apply));
}
}
而 C 代碼看起來像這樣:
void register(void (*f)(void (*)(int), int)) {
...
}
并不需要transmute !
在 C 中調(diào)用 Rust 代碼
你可能會(huì)希望這么編譯 Rust 代碼以便可以在 C 中調(diào)用。這是很簡(jiǎn)單的,不過需要一些東西:
#[no_mangle]
pub extern fn hello_rust() -> *const u8 {
"Hello, world!\0".as_ptr()
}
# fn main() {}
extern 使這個(gè)函數(shù)遵循 C 調(diào)用約定,就像之前討論外部調(diào)用約定時(shí)一樣。no_mangle 屬性關(guān)閉Rust的命名改編,這樣它更容易鏈接。
FFI 和 panic
當(dāng)使用 FFI 時(shí)留意panic! 是很重要的。一個(gè)跨越FFI邊界的panic! 是未定義行為。如果你在編寫可能 panic 的代碼,你應(yīng)該使用 catch_unwind() 在一個(gè)閉包中運(yùn)行它:
use std::panic::catch_unwind;
#[no_mangle]
pub extern fn oh_no() -> i32 {
let result = catch_unwind(|| {
panic!("Oops!");
});
match result {
Ok(_) => 0,
Err(_) => 1,
}
}
fn main() {}
注意 catch_unwind() 只捕獲那些不嚴(yán)重的(unwinding) panic,不是那些會(huì)終止進(jìn)程的。查看 catch_unwind() 的文檔來獲取更多信息。
表示 opaque 結(jié)構(gòu)體
有時(shí)一個(gè) C 庫想要提供某種指針,不過并不想讓你知道它需要的內(nèi)部細(xì)節(jié)。最簡(jiǎn)單的方法是使用一個(gè)void * 參數(shù):
void foo(void *arg);
void bar(void *arg);
我們可以使用c_void 在 Rust 中表示它:
# #![feature(libc)]
extern crate libc;
extern "C" {
pub fn foo(arg: *mut libc::c_void);
pub fn bar(arg: *mut libc::c_void);
}
# fn main() {}
這是處理這種情形完美有效的方式。然而,我們可以做的更好一點(diǎn)。為此,一些 C 庫會(huì)創(chuàng)建一個(gè)struct ,結(jié)構(gòu)體的細(xì)節(jié)和內(nèi)存布局是私有的。這提供了一些類型安全性。這種結(jié)構(gòu)體叫做opaque 。這是一個(gè) C 的例子:
struct Foo; /* Foo is a structure, but its contents are not part of the public interface */
struct Bar;
void foo(struct Foo *arg);
void bar(struct Bar *arg);
在 Rust 中,讓我們用enum 創(chuàng)建自己的 opaque 類型:
pub enum Foo {}
pub enum Bar {}
extern "C" {
pub fn foo(arg: *mut Foo);
pub fn bar(arg: *mut Bar);
}
# fn main() {}
通過一個(gè)沒有變量的enum ,我們創(chuàng)建了一個(gè)不能實(shí)例化的 opaque 類型,因?yàn)樗鼪]有變量。不過因?yàn)槲覀兊?code>Foo和Bar 是不同類型,我們可以安全的獲取這兩個(gè)類型,所以我們不可能不小心向bar() 傳遞一個(gè)Foo 的指針。
|