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的数值计算库,比如nalgebra,ndarray,rust-gsl等。但是,我个人对库的性能和成熟度还是比较怀疑的。就比如说Python的Scipy中的Bessel函数,在计算方法上跟GSL比较起来,还有一丢丢的精度差别。
所以,我个人还是倾向于直接调用成熟的C/C++数值计算库,来保证自己可以偷懒。
下面,小龙只需要再整几个容器,就能完成所有的工程计算任务了。
文章标签
|-->rust |-->engineering |-->tutorial |-->unsafe
- 本站总访问量:loading次
- 本站总访客数:loading人
- 可通过邮件联系作者:Email大福
- 也可以访问技术博客:大福是小强
- 也可以在知乎搞抽象:知乎-大福
- Comments, requests, and/or opinions go to: Github Repository