原文
为了与其他语言通信,Rust
提供了(FFI)
外部函数接口.FFI
是Rust
和C
间的函数调用
,与C函数
调用有相同
性能的零成本
抽象.
FFI
绑定还可利用(如所有权和借用
)语言功能来提供强制指针和其他资源
协议的安全接口
.
Rust
与C对话从Rust
调用C代码
的简单示例开始.如下为C
代码:
int double_input(int input) {
return input * 2;
}
要从Rust
调用它,可如下编写
程序:
extern crate libc;
//外部仓库.
extern {
fn double_input(input: libc::c_int) -> libc::c_int;//写签名.
}
fn main() {
let input = 4;
let output = unsafe { double_input(input) };
//不安全中调用.
println!("{} * 2 = {}", input, output);
}
就这样!在源码
级,除了声明签名
外,就行了.
但有些细节:
首先,看到extern crate libc
.此libc
仓库在与C语言
通信时,为FFI
绑定提供了许多有用
的类型定义,且确保C和Rust
在跨语言边界
类型上保持一致.
再看:
extern {
fn double_input(input: libc::c_int) -> libc::c_int;
}
在Rust
中,这是外部可用
函数的声明.可按C头文件
对待.这里编译器要了解函数的输入和输出
,可在上面看到,这与C中
定义匹配.
接着,程序主体:
fn main() {
let input = 4;
let output = unsafe { double_input(input) };
println!("{} * 2 = {}", input, output);
}
在此看到了Rust
中FFI
的关键
方面,即不安全
块.编译器对double_input
的实现一无所知,因此它必须假设调用
外部函数时,都可能内存
不安全.
再看看是否可验证
零成本.
为了了解Rust
做了什么,直接进入
上述main
函数对double_input
调用的汇编
代码:
mov $0x4,%edi
callq 3bc30 <double_input>
如前,就是这样!在此可见,把参数移动
到位后,从Rust
调用C函数
恰好涉及一个调用
指令,这与C语言
中的成本
完全相同.
在Rust
中绑定C库
时,不仅是零成本
,还可比C更安全!
如,考虑解析tarball
的C库
.该库公开取读tarball
中每个文件内容
的函数
,可能如下:
//在`tarball`中,在给定`索引`处,取文件的数据,如果不存在`该文件`,则返回`NULL`.如果成功,用文件大小`填充`,`"size"`指针.
const char *tarball_file_data(tarball_t *tarball, unsigned index, size_t *size);
但是,此函数
假定返回的char*
指针生命期不超过输入的tarball
.绑定到Rust
中时,此API
可能如下:
pub struct Tarball { raw: *mut tarball_t }
impl Tarball {
pub fn file(&self, index: u32) -> Option<&[u8]> {
unsafe {
let mut size = 0;
let data = tarball_file_data(self.raw, index as libc::c_uint, &mut size);
if data.is_null() {
None
} else {
Some(slice::from_raw_parts(data as *const u8, size as usize))
}
}
}
}
这里的*mut tarball_t
指针,由Tarball
所有,并由它负责析构和清理
.
此外,file
方法返回生命期
隐式与源tarball
自身生命期
相关联(&self
参数)的借用
切片.
这是Rust
指示,只能在tarball
的生命期内使用返回
的slice
,静态避免
了直接使用C
时易产生的悬挂针
错误.
因为Rust
的静态检查,使用Rust
端的API
根本不可能造成段错误
.所有这些
都是零成本
的:无额外分配
或成本,可在Rust
中表示C
语言中的原始类型
.
Rust
令人惊叹的社区
,已围绕现有的C库构建了一些实质性的安全绑定,包括OpenSSL,libgit2,libdispatch,libcurl,sdl2,UnixAPI
和libsodium
.
1
2
3
4
5
6
7
更多.
Rust
对话零成本FFI
不仅适合Rust
调用C
,也适合C
调用Rust
!
首先,从Rust
代码开始:
#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
input * 2
}
与之前Rust
代码一样,首先,用#[no_mangle]
属性标记了函数定义
.
指示编译器
不要装饰double_input
函数的符号名
.Rust
使用类似C++
混杂名来确保库名
不会相互冲突.
extern
,可与C函数
兼容.编译为lib
文件,而不是rlib
库.
#include
#include
extern int32_t double_input(int32_t input);
int main() {
int input = 4;
int output = double_input(input);
printf("%d * 2 = %d\n", input, output);
return 0;
}
Rust
没有垃集和运行时
,因此实现
了从C到Rust
的无缝过渡
.外部C代码
不需要安装Rust
执行设置,使得过渡成本
非常低.