设为首页 加入收藏

TOP

那些C语言缺失的,我在Rust里找到了(一)
2018-03-18 16:21:40 】 浏览:824
Tags:那些 语言 缺失 我在 Rust 找到

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


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


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


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


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


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


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


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


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


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


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


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


调试Mesa,却无果。


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


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


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


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


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


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


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


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


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,用于维护迭代器状态。


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


我之前发表了一篇有关C语言缺少字符串切割特性的文章(https://people.gnome.org/~federico/blog/rant-on-string-slices.html),解释了C语言的这个痛点。


以前实现依赖管理需要:


而在Rust里,只需要编写一个Cargo.toml文件,然后在文件里指明依赖库的版本。这些依赖库会被自动下载下来,或者从某个指定的地方获取。


C语言的单元测试非常困难,原因如下:


而在Rust里,可以在任何地方写这样的代码:


#[test]
fn test_that_foo_works() {
  assert!(foo() == expected_result);
}


然后运行cargo test运行单元测试。这些代码只会被链接到测试文件中,不需要手动编译任何东西,不需要编写Makefile文件或抽取内部函数用于测试。


对我来说,这个功能简直就是杀手锏。


在Rust中,可以将使用Markdown语法编写的注释生成文档。注释里的测试代码会被作为测试用例执行。也就是说,你可以在解释如何使用一个函数的同时对它进行单元测试:


/// Multiples the specified number by two
///
/// ```
/// assert_eq!(multiply_by_two(5), 10);
/// ```
fn multiply_by_two(x: i32) -> i32 {
  x * 2
}


注释中的示例代码被作为测试用例执行,以确保文档与实际代码保持同步。


Rust的卫生宏避免了C语言宏可能存在的问题,比如宏中的一些东西会掩盖掉代码里的标识符。Rust并不要求宏中所有的符号都必须使用括号,比如max(5 + 3, 4)。


在C语言里,很多bug都是因为在无意中将int转成short或char而导致,而在Rust里就不会出现这种情况,因为它要求显示转型。


这个就不用再多作解释了。


在Rust的“安全”模式下编写的代码(unsafe{}代码块之外的代码)如果出现了未定义行为,可以直接把它当成是一个bug来处理。比如,将一个负整数右移,这样做是完全可以的。


在对一个枚举类型进行switch操作时,如果没有处理所有的值,gcc编译器就会给出警告。


Rust提供了模式匹配,可以在match表达式里处理枚举类型,并从单个函数返回多个值。


impl f64 {
  pub fn sin_cos(self) -> (f64, f64);
}


let angle: f64 = 42.0;
let (sin_angle, cos_angle) = angle.sin_cos();


match表达式也可以用在字符串上。是的,字符串。


let color = "green&qu

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Spring 数据库连接(Connection).. 下一篇TensorFlow 基本用法示例

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目