Rust-初见

Rust是Mozilla基金会提出的新一代内存安全的C++和C的替代品,可以用来制造操作系统内核,后端应用程序,跨平台应用程序等等,包含丰富的std支持库,内存安全特性,和爆发式增长的生态。

其实知道Rust语言是很久以前的事情了,可以追溯到2019年我还在写EasyCrossPlatform这个C++库的时候,当时我觉得C++的语法糖真的很酷炫,很优雅,但是非常痛苦,在构建问题上花费了太多时间。

C++最让人诟病的几点是:

  1. 指针释放问题
    1. 可能会造成内存泄露,多次释放指针导致权限问题的Segmentation Fault
    2. 被弱指针和所有权指针部分解决,但是C++的原生指针历史遗留问题注定了这个内存管理问题的存在。
  2. 多进程/协程支持非常有限
    1. 多进程std支持在C++11标准中提出,现在基本所有编译器都支持
    2. 但问题是指针问题导致的两个内存同时写可能需要调用互斥锁(mutex)或者atomic标准库,没有更优雅的解决方式,如果一个进程使用了一个变量,很难保持对其他进程使用情况的追踪(需要程序员自己keep track)。
  3. std标准推进缓慢
    1. 乃至于2015年立项的Networking Draft到了2022年都没有进入C++23标准。
    2. 非常离谱,强烈阻碍了C++生态的发展
  4. 没有统一的包管理
    1. 致命!装一个库可能得下3-4个包管理软件例如make,cmake,vcpkg,等等
    2. 有的时候甚至不知道动态链接的库的头文件该怎么找

下面我将叙述一下rust是怎么一个个解决这些问题的

首先,Rust引进了一整套关于C++内存管理方面痛点的解决方案(当然同时也制造了一些其他的痛点),其中最重要的就是所有权概念。

我们直接来看代码

在C++中,如果我们创建一个字符串类型(std::string)

1
2
3
4
5
6
7
8
9
10
11
include <string>
include <iostream>

using namespace std;

int main(int argc, char** args){
string a = "我爱秋风";
string &b = a;
cout << a << endl;
cout << b << endl;
}

这套代码将在控制台中输出

1
2
我爱秋风
我爱秋风

让我们用Rust写出同样的逻辑

1
2
3
4
5
6
7
8
fn main(){
let a = String::from("我爱秋风");
let b = a;
//由于String类型存于堆(Heap)中,不继承Copy Trait (可以参照Rust官方手册关于Copy Trait的内容),Rust会默认传引用(类似于Java对Object的引用传递)而不是像C++调用assign operator
//如果想要复制内容, 可以设置b 为 a.clone()
println!("{}",a);
println!("{}",b);
}

而这段代码将会报编译时错误(Compile-Time Error), 为什么呢?因为Rust的所有权机制
这个机制看起来好像很傻,因为Java不会爆出这种错误,整个代码都可以顺畅的运行,但Rust就不可以。
但这是因为Rust是实时语言,没有垃圾收集器(GC),而Java有GC,程序每运行几秒就会有几毫秒或者几微秒的GC。GC导致Java在使用栈空间的时候不需要做任何内存释放工作,因为每次GC Sweep都会自动做引用计数并收集垃圾,而RustC++这类实时语言需要在某个变量不需要用了以后立即负责释放掉内存的空间。

这样做的好处和坏处分别是什么呢?

  1. 有了GC可以大范围的节省开发成本
    1. 程序员不再需要管理内存释放
  2. 但是GC对于一些对于实时性要求高的任务可能不太适合,因为运行时什么时候进行GC Sweep是不太能预测的。
    1. 火箭发射 / 手术或者工业界的控制程序 / …

C++和Rust都是做实时内存释放,但是C++要求程序员手动释放内存,而Rust通过变量作用域和所有权这两大神器可以帮助程序员自动释放内存。
这样一方面可以帮助简化单线程内存方面的管理,多线程方面,程序员也可以很清楚的区分哪些变量只能被哪些线程来访问。
但代价是什么呢?

  1. 程序员需要管理变量的作用域
    1. Rust对于引用,自动引用追踪的管理及其严格,这要求程序员在编写时时刻想着引用,所有权,和作用域的概念,我这个C++和61C脑子一时半会还转不过来(主要是C++太不安全了写惯了哈哈哈)
    2. 并且因为Halting Problem(一个CS领域非常有名的问题),明明是安全的内存使用缺无法在编译时检测是否安全,Rust将默认弹出错误,这时可以通过使用一个特殊类型来套过编译时检查转而使用运行时检查。

说了那么多,其实Rust既有优点又有缺点,但是最近由于Networking标准在C++23上迟迟得不到讨论我实在是对这个傻逼语言失望了。Cargo这个集中的包管理软件又深得我心,所以总的来说Rust我个人的态度还是很喜欢的(尤其是它绝大多数的语法与C++和Dart特别像)。

下面我的计划应该是用Rust试着写一下OpenAPI的后端(Go语法实在是爱不起来,没有OOP…)。