<-- Home |--rust |--rfe

RFE 006: 工程师(粗)Rust入门之大胆尝试Unsafe

什么是Unsafe?

为什么前脚我激进到不要用mut,后脚又要提unsafe?其实这是两个不同的概念。

因为mut关键字引入了可变性,而可变性是值编程范式的反面。我的理解是如果要真正简单的编程概念入门,比如教小龙入门Rust,我们先不要搞什么可写变量。因为,所有的复杂情况基本上都来自于可变性。

  • 仅仅读变量:始终是安全的,可以多处读,都没问题;
  • 单写变量:也可以是安全的,只要没有读操作;
  • 读写变量:就会形成竞争关系,需要复杂的规则来保证安全性。

但是,Rust的unsafe就只是一个“信任我,我知道自己在干什么”的标记。它并不一定意味着可变性。

实际上,为什么要需要unsafe?其实还是因为Rust在跟C/C++打交道的方面做得特别出色。这是一个聪明的策略,在我看来。

特别是,小龙和我想要做的是用Rust来写工程中的计算程序(基本上是Formulas Translation,Fortran)的射程。而这类计算程序,都常常需要调用一些真的Fortran或者C/C++的数值计算库。要想调用这些库,Rust就必须要有一个和C/C++互操作的能力。

而,Rust的互操作能力,基本上就需要unsafe。因为C/C++的内存模型和Rust的内存模型是不一样的。Rust无法保证从C/C++传过来的指针是安全的,所以必须要用unsafe来标记这些操作。

大胆尝试Unsafe来调用库

调库调库,Python为什么这么流行,是因为Python把Unix/Linux下的各种C库都能调用起来了,成为一个强大的系统库前端语言。

那么Rust调用ABI兼容的C库,实际上也是类似的思路。

例子:调用gsl

GNU科学库(GSL)是一个用C语言编写的数值计算库,提供了丰富的数学函数和算法。我们可以通过Rust的unsafe代码来调用GSL的函数。

首先,我们需要在Rust项目中添加gsl库的依赖。然后,我们可以使用extern块来声明我们要调用的GSL函数。

 1#[link(name = "gsl")]
 2#[link(name = "gslcblas")]
 3unsafe extern "C" {
 4    fn gsl_sf_bessel_J0(x: f64) -> f64;
 5}
 6
 7fn main() {
 8    let x = 5.0;
 9
10    let result = unsafe { gsl_sf_bessel_J0(x) };
11
12    println!("GSL Bessel J0({}) = {}", x, result);
13}

在这个例子中,我们使用extern "C"块来声明GSL的gsl_sf_bessel_J0函数。然后,在unsafe块中调用这个函数,并将结果打印出来。

1rustc gsl-go.rs
2./gsl-go
3J0(5.0) = -0.1775967713143383

真的就是这么简单!

当然,还得安装GSL

我们当然需要先安装GSL库。可以通过包管理器安装,例如在WSL/Ubuntu上:

1sudo apt-get install libgsl-dev

这就完成了GSL库的安装。

Wrapper

当然,我们一般而言,还会继续封装一层Rust的安全接口,来调用这些unsafe代码。这样可以让我们的Rust代码更加安全和易用。

1fn bessel_j0(x: f64) -> f64 {
2    unsafe { gsl_sf_bessel_J0(x) }
3}

那么有人就要问了,包来包去,会不会效率下降?当然会有效率的损失,但是怎么损失也到不了Python调用C库那么严重。所以,这个损失是可以接受的。

我实际上还做了一个带哦用100万次GSL Bessel函数的基准测试,发现Rust调用GSL的效跟C直接调用GSL的效率差不多,在用上面的代码包一层的情况下大概只有2~3%左右的损失。

链接选项

当然,还有要给需要解释的问题就是#[link(name = "gsl")]这个属性。它的意思是告诉Rust编译器在链接阶段要链接GSL库。这就类似于C/C++中的-lgsl选项。这样,Rust编译器才能找到GSL库的实现。

当然Rust的这个宏属性还提供链接为动态库还是静态库的选项,可以参考Rust的官方文档。

1#[link(name = "gsl", kind = "dylib")] // 动态库
2// 或者
3#[link(name = "gsl", kind = "static")] // 静态库

静态库最后生成的可执行文件会比较大一些,但是运行时不需要依赖外部的动态库文件。动态库则相反。

个人偏见

当然,我们也可以用纯Rust的数值计算库,比如nalgebrandarrayrust-gsl等。但是,我个人对库的性能和成熟度还是比较怀疑的。就比如说Python的Scipy中的Bessel函数,在计算方法上跟GSL比较起来,还有一丢丢的精度差别。

所以,我个人还是倾向于直接调用成熟的C/C++数值计算库,来保证自己可以偷懒。

下面,小龙只需要再整几个容器,就能完成所有的工程计算任务了。


文章标签

|-->rust |-->engineering |-->tutorial |-->unsafe


GitHub