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

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

依赖管理

以前实现依赖管理需要:

手动调用或通过自动化工具宏来调用pkg-config。

指定头文件和库文件路径。

基本上需要人为确保安装了正确版本的库文件。

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

测试

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

内部函数通常都是静态的。也就是说,它们无法被外部文件调用。测试程序需要使用#include指令把源文件包含进来,或者使用#ifdefs在测试过程中移除这些静态函数。

需要编写Makefile文件将测试程序链接到其中的部分依赖库或部分代码。

需要使用测试框架,并把测试用例注册到框架上,还要学会如何使用这些框架。

而在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
}

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

卫生宏(Hygienic Macro)

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

没有自动转型

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

不会出现整型溢出

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

在安全模式下,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";

match color {
  "red"  => println!("it's red"),
  "green" => println!("it's green"),
  _    => println!("it's something else"),
}

你是不是很难猜出下面这个函数是干什么用的?

my_func(true, false, false)

但如果在函数的参数上使用模式匹配,那么事情就会变得不一样:

pub struct Fubarize(pub bool);
pub struct Frobnify(pub bool);
pub struct Bazificate(pub bool);

fn my_func(Fubarize(fub): Fubarize,
      Frobnify(frob): Frobnify,
      Bazificate(baz): Bazificate) {
  if fub {
    ...;
  }

if frob && baz {
    ...;
  }
}

...

my_func(Fubarize(true), Frobnify(false), Bazificate(true));

标准的错误处理

在Rust里,不再只是简单地返回一个布尔值表示出错与否,也不再简单粗暴地忽略错误,也不再通过非本地跳转来处理异常。

#[derive(Debug)]

在创建新类型时(比如创建一个包含大量字段的struct),可以使用#[derive(Debug)],Rust会自动打印该类型的内容用于调试,不需要再手动编写函数去获取类型的信息。

闭包

不再需要使用函数指针了。

结论

在多线程环境里,Rust的并发控制机制可以防止出现数据竟态条件。我想,对于那些经常写多线程并发代码的人来说,这会是个好消息。

C语言是一门古老的语言,用它来编写单处理器的Unix内核或许是个不错的选择,但对于现今的软件来说,它算不上好��言。

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

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