0%

Rust跨语言

语言交互

  • 外部函数接口(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编译器内部函数的ABI
    • extern "rust-call",Fn::call的ABI
    • extern "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平台交互

使用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.wasm

      WebAssembly开发生态

  • 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
  • 除此之外,社区的一些有代表性的框架:
    • stdweb:Rust和WebAssembly实现的Web客户端标准库,可能会被web-sys替代
    • cargo-web:方便编写Web客户端的Cargo子命令库
    • yew:构建客户端Web应用的Rust框架,基于stdweb库
    • percy:实现虚拟Dom,根据服务端HTML字符串渲染到浏览器的Dom
    • ruukh:实验性Rust Web前端框架

扩展