那些C语言缺失的,我在Rust里找到了

Librsvg似乎已经到了这样的一个地步:直接将C语言开发的部分改用Rust要比继续使用C语言来得更加容易。更何况,它越来越多的代码已经使用了Rust

近来,我在C语言和Rust之间来回切换。在我看来,C语言似乎变得更像老古董。

C语言挽歌

我大概在24年前就爱上了C语言。当时,我通过一本西班牙语版的“The C Programming Language”(第二版,作者是Brian Kernighan和Dennis Ritchie,所以有时候也用K&R来称呼这本书)来学习C语言。在这之前,我用过Turbo Pascal,它也有指针,也需要手动管理内存,而C语言在当时是新生事物,但十分强大。

K&R因其独特的文风和简洁明了的代码风格而闻名。它甚至还教你如何自己实现简单的malloc()和free()函数,这实在太有意思了。而且,这门语言本身的一些特性也可以通过自身来实现。

在接下来的几年,我一直使用C语言。它是一门轻巧的编程语言,使用差不多2万行代码实现了Unix内核。

GIMP和GTK+让我学会了如何使用C语言来实现面向对象编程,GNOME让我学会了如何使用C语言维护大型的软件项目。一个2万行代码的项目,一个人花上几周就可以完全读懂。

但现在的代码库规模已经不可同日而语,我们的软件对编程语言的标准库有了更高的期望。

C语言的一些好的体验

第一次通过阅读POV-Ray源代码学会如何在C语言中实现面向对象编程。

通过阅读GTK+源代码了解C语言代码的清晰、干净和可维护性。

通过阅读SIOD和Guile的源代码,知道如何使用C语言实现Scheme解析器。

使用C语言写出GNOME Eye的初始版本,并对MicroTile渲染进行调优。

C语言的一些不好的体验

在Evolution团队时,很多东西老是崩溃。那个时候还没有Valgrind,为了得到Purify这个软件,需要购买一台Solaris机器。

调试gnome-vfs线程死锁问题。

调试Mesa,却无果。

接手Nautilus-share的初始版本,却发现代码里面居然没有使用free()。

想要重构代码,却不知道该如何管理好内存。

想要打包代码,却发现到处是全局变量,而且没有静态函数。

但不管怎样,还是来说说那些Rust里有但C语言里没有的东西吧。

自动资源管理

我读过的第一篇关于Rust的文章是“Rust means never having to close a socket”()。Rust从C++那里借鉴了一些想法,如RAII(Resource Acquisition Is Initialization,资源获取即初始化)和智能指针,并加入了值的单一所有权原则,还提供了自动化的决策性资源管理机制。

自动化:不需要手动调用free()。内存使用完后会自动释放,文件使用完后会自动关闭,互斥锁在作用域之外会自动释放。如果要封装外部资源,基本上只要实现Drop这个trait就可以了。封装过的资源就像是编程语言的一部分,因为你不需要去管理它的生命周期。

决策性:资源被创建(内存分配、初始化、打开文件等),然后在作用域之外被销毁。根本不存在垃圾收集这回事:代码执行完就都结束了。程序数据的生命周期看起来就像是函数调用树。

如果在写代码时老是忘记调用这些方法(free/close/destroy),或者发现以前写的代码已经忘记调用,甚至错误地调用,那么以后我再也不想使用这些方法了。

泛型

Vec<T>真的就是元素T的vector,而不只是对象指针的数组。在经过编译之后,它只能用来存放类型T的对象。

在C语言里需要些很多代码才能实现类似的功能,所以我不想再这么干了。

trait不只是interface

Rust并不是一门类似Java那样的面向对象编程语言,它有trait,看起来就像是Java里的interface——可以用来实现动态绑定。如果一个对象实现了Drawable,那么就可以肯定该对象带有draw()方法。

不过不管怎样,trait的威力可不止这些。

关联类型

trait里可以包含关联类型,以Rust的Iterator这个trait为例:

pub trait Iterator {
  type Item;
  fn next(&mut self) -> Option<Self::Item>;
}

也就是说,在实现Iterator时,必须同时指定一个Item类型。在调用next()方法时,如果还有更多元素,会得到一个Some(用户定义的元素类型)。如果元素迭代完毕,会返回None。

关联类型可以引用其他trait。

例如,在Rust里,for循环可以用于遍历任何一个实现了IntoIterator的对象。

pub trait IntoIterator {
  /// 被遍历元素的类型
  type Item;
  type IntoIter: Iterator<Item=Self::Item>;
  fn into_iter(self) -> Self::IntoIter;
}

在实现这个trait时,必须同时提供Item类型和IntoIter类型,IntoIter必须实现Iterator,用于维护迭代器状态。

通过这种方式就可以建立起类型网络,类型之间相互引用。

字符串切割

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/7065f4d503412e96ba55ebaa85749bf0.html