语言交互
- 外部函数接口(Foreign Function Interface,FFI)用于规范语言间调用的语法特征。其他有java的JNI(Java Native Interface),一样的意思
- 应用程序二进制接口(ABI)涵盖内容:
- 调用约定。参数、函数、返回值
- 内存布局。大小和对齐方式
- 处理器指令集。不同平台实现
- 目标文件和库的二进制形式。
- Rust中使用FFI需要通过extern和extern块对FFI接口进行标注,编译时会由LLVM默认生成C-ABI
链接与Crate Type
- 链接产生于程序开发的模块化。C和C++每个文件都是一个编译单元,Rust是以包(crate)为编译单元
- 链接过程就是编译器经过符号解析、存储空间分配和重定位的过程,最终生成可执行文件或库。
- 库分为静态库和动态库。静态库是普通目标文件集合的简单拼接,使用简单,但浪费空间;动态库则是把链接过程延迟到运行时,如运行时重定位,这比较省空间
- Rust支持四种库:Rust的dylib、rlib,和其他语言的cdylib、staticlib
- Rust库类型的命令行参数或crate_type属性:
--crate-type=bin
或#[crate_type = "bin"]
,表示是可执行文件,程序必须包含main函数--crate-type=lib
或#[crate_type = "lib"]
,表示生成Rust库,什么库编译器决定,一般是rlib静态库--crate-type=rlib
或#[crate_type = "rlib"]
,生成静态Rust库--crate-type=dylib
或#[crate_type = "dylib"]
,生成动态Rust库--crate-type=staitclib
或#[crate_type = "staticlib"]
,将生成静态系统库,Rust编译器永远不会链接它。用于和C语言链接--crate-type=cdylib
或#[crate_type = "cdylib"]
,生成动态系统库
交叉编译
- 本地平台上编译出需要在其他平台运行的程序叫做交叉编译。
- 可以使用rustc进行交叉编译,只需要传递target参数即可:如
$ rustc --target=arm-unknown-linux-gnueabihf hello.rs
- 也可以使用Cargo交叉编译,方法类似,在cargo
build命令传递–target参数即可。Rust默认使用cc作为交叉编译的链接器,修改配置文件可以指定链接器:1
2
3
4
5# $ car ~/.cargo/config
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc-4.8" # 指定链接器
# 使用cargo交叉编译
# $ cargo build --target=arm-unknown-linux-gnueabihf - target triple格式是
{arch}-{vendor}-{sys}-{abi}
,分别代表编译程序的主机系统、供应商、操作系统、ABI接口,最后abi有时候可以省略
extern语法
- 有extern关键字和extern块。有三个extern API字符串是跨平台的:
extern "Rust"
、extern "C"
、extern "system"
- 另外有三个Rust编译器专用的ABI字符串:
extern "rust-intrinsic"
,代表Rust编译器内部函数的ABIextern "rust-call"
,Fn::call的ABIextern "platform-intrinsic"
,特定平台内在函数的ABI
与C/C++交互
Rust调用C函数
- 首先新建C/C++源代码文件夹,编写好代码后,使用
extern "C" {}
外暴露接口 - 在caogo.toml配置文件中,
[build-dependencies]
中使用cc库,然后在[package]
中将某个build.rs文件指定为自动化编译配置文件 - 在build.rs中引进包
extern crate cc
,使用cc.Build::new()
等方法初始化,在Rust源文件中正常使用时,用extern { ... }
声明指定的外部接口
C调用Rust库
- 首先在cargo.toml配置文件中
[lib]
设置Rust链接库名称,如:name = "callrust"
,将在Rust同级目录下写C头文件用于暴露Rust接口,并设置crate-type = ["staticlib", "cdylib"]
用于生成什么类型的链接库,库将产生在target/Debug下 - 编写Rust程序,将对应接口写在callrust.c中,并编写markdown文件:cargo用于编译Rust代码,gcc用于链接库并生成对应二进制文件
- 在另起的C源代码文件夹中直接
#include "callrust"
,然后调用接口函数即可
第三方工具
- rust-bindgen:根据头文件自动生成Rust FFI的C绑定,支持部分C++功能
- cbindgen:根据Rust代码自动生成头文件
- ctest:为Rust FFI的C绑定自动生成测试文件
- 移动平台下也有两个库:
- cargo-lipo:提供
cargo lipo
命令,自动生成用于iOS的通用库 - jni:提供Rust的JNI绑定,用于和Android平台交互
- cargo-lipo:提供
使用Rust提升动态语言性能
- 使用Rust为动态库编写扩展,能保证性能,还能提升内存安全
- 动态语言有自己的虚拟机,调用Rust代码不能像C/C++_直接链接Rust的链接库获取函数调用信息。因此动态语言提供FFI 基本都是基于libffi库来实现动态调用C函数的能力,兼容C-ABI的链接库都可以直接被动态调用。libffi库是动态语言虚拟机和二进制的一道桥梁
Rust与WebAssembly
WebAssembly是兴起的新的字节码格式,缩写是“WASM”,它是一个面向Web通用二进制和文本格式项目,它是为了作为C/C++/Rust语言的一种编译目标,在客户端提供一种接近本地运行速度的多语言编写代码的方式。某种意义上说,WebAssembly是一种中间语言(
IR),就像汇编(Assembly)语言那样是所有语言转换成机器码的通用底层语言,WebAssembly就是面向Web的汇编
- WebAssembly比JavaScript快的原因:
- WebAssembly体积更小,下载和解析更快。
- WebAssembly不受JavaScript约束,可以利用更多CPU特性。如64位整数、内存读写偏移量、非内存对齐读写和多种CPU指令等。
- 生成WebAssembly编译器工具链的优化和改进。如在Rust中,可以用wasm-gc工具优化生成的wasm文件大小。
- WebAssembly不需要垃圾回收。内存操作是手动控制,但也没必要担心内存泄漏,它的内存空间是JavaScript分配的对象,最终Javascript的GC管理
- WebAssembly在Web只是一个应用环境,WebAssembly还可以应用其他领域:桌面图形化程序、区块链智能合约和编写操作系统微内核
WebAssembly要点
- 模块。一个.wasm文件局势一个基本编译单位
- 线性内存。用于和JavaScript通信,是个可变大小的ArrayBuffer,由JavaScript分配
- 表格。用于存放函数引用,支持动态调用函数
- 实例。同一个模块的多个实例共享相同的内存和表格
- 栈式机器。WebAssembly指令的运行基于栈式机器定义,每种指令都是在站上进行出栈和入站操作
使用Rust开发WebAssembly
- 可以手写wat文本格式开发wasm模块,但效率不高,WebAssmebly设计之初是为了作为编译目标存在的,因此可以作为很多变成语言的编译目标:
- C/C++,可以通过EmScripten工具来编译到wasm。EmScripten是个LLVM后端工具,可以将LLVM中间码编译到asm.js。所以C/C++的编译流程可以通过任何一个LLVM前端工具生成LLVM IR,然后通过EmScripten生成asm.js,最后通过WebAssembly编译工具链Binaryen将asm.js生成wasm二进制格式。一些不支持wasm的浏览器也可以使用asm.js来代替
- Rust,支持wasm的两种编译目标:
- wasm32-unknown-unknown,使用LLVM WebAssembly Backend和lld链接器
- wasm32-unknown-emscripten,继续使用EmScripten,和C/C++类似
- 搭建wasm开发环境:
1
2$ rustup toolchain install nightly
$ rustup target add wasm32-unknwon-unknown --toolchain nightly - 生成wasm例子:
1
2
3
4$ cargo install wasm-gc
$ cargo build --target wasm32-unknown-unknown
$ cp target/wasm32-unknown-unknown-unknown/debug/hello_wasm.wasm output
$ wasm-gc output/hello_wasm.wasm output/small_hello.wasmWebAssembly开发生态
- Rust开发效率还是很低,WebAssembly标准值定义了四种类型:i32、i64、f32和f64。因此,Rust官方打造了一系列工具:
- wasm-bindgen:它的核心是促进JavaScript和Rust之间使用wasm进行通信,允许开发者直接使用Rust的结构体、JavaScript的类、字符串等类型
1
$ cargo +nightly install wasm-bindgen-cli
- wasm-pack:一站式构建、发布Rust编译的wasm到npm平台。不需要安装npm、node.js等JavaScript环境,wasm-pack会编译并优化生成JavaScript绑定,然后发布到npm中
1
2
3$ cargo install wasm-pack
# 在项目的根目录下执行命令,自动生成JavaScript相关文件,方便打包wasm到npm平台
$ wasm-pack build - cargo-generate:直接生成wasm-bindgen和wasm-pack项目模板,方便开发
1
2
3$ cargo install cargo-generate
# 命令生成模板会在Cargo.toml文件配置好wasm-bindgen,需要先安装wasm-bindgen-cli工具
$ cargo-generate --git https://github.com/your-responsbility
- wasm-bindgen:它的核心是促进JavaScript和Rust之间使用wasm进行通信,允许开发者直接使用Rust的结构体、JavaScript的类、字符串等类型
- 除此之外,社区的一些有代表性的框架:
- stdweb:Rust和WebAssembly实现的Web客户端标准库,可能会被web-sys替代
- cargo-web:方便编写Web客户端的Cargo子命令库
- yew:构建客户端Web应用的Rust框架,基于stdweb库
- percy:实现虚拟Dom,根据服务端HTML字符串渲染到浏览器的Dom
- ruukh:实验性Rust Web前端框架