开源中国

我们不支持 IE 10 及以下版本浏览器

It appears you’re using an unsupported browser

为了获得更好的浏览体验,我们强烈建议您使用较新版本的 Chrome、 Firefox、 Safari 等,或者升级到最新版本的IE浏览器。 如果您使用的是 IE 11 或以上版本,请关闭“兼容性视图”。
Rust 语言如何帮助你防止 bug - 技术翻译 - 开源中国社区

Rust 语言如何帮助你防止 bug 【已翻译100%】

标签: Rust
oschina 推荐于 10个月前 (共 24 段, 翻译完成于 07-30) 评论 7
收藏  
19
推荐标签: Rust 待读

引言

如果你曾经编写过任何规模大小的程序,你可能会遇到各种错误。你在编码时产生的微小的错误会导致你的程序执行失败。程序越复杂,发生错误的概率越高!

为了修复和防止错误,有很多方法可供程序员使用。其中一个是在运行程序之前确定程序的正确性:静态类型。这种技术是设计编程语言的一部分,并且可以防止简单的错误,例如尝试使用字符串作为整数,或者比较类型不同的对象,例如 Car 和 Book

Tocy
 翻译得不错哦!

我的个人观点是,编程语言及其实现应该尽可能地捕获程序员所犯的错误,从而使得他们能构建更好更安全的软件。虽然静态类型使得语言更加复杂和难以学习,但它为程序员提供了一个安全的机制,我相信这是非常值得的。

Rust 语言实现了这样的静态类型系统,并提供了捕获错误的新方法,这在其他语言中运行时会导致崩溃。在这篇文章中,我将会讲解其中一些方法。

Tocy
 翻译得不错哦!

Null 返回

子程序由于遇到某种边界情况而无法返回结果并不罕见。想想一个例子:在数组中查找并不包含某项元素的索引,从空堆栈中弹出元素或在很小的数组上的通过索引访问元素。这些处理方式在不同语言之间差别很大。最常见的处理方式似乎是抛出异常或返回 null(或-1)。

如果你忘记检查 -1 或 null 或捕获异常,你的程序将会崩溃。然而,Rust 有一个确保在编译时捕获和处理错误的策略。

Tocy
 翻译得不错哦!

Option 类型

使用Rust的标准库Option来处理函数边界情况。这是一个枚举类型(如果你熟悉C语言的话,有点像一个联合体)包含有两个可能的值。这是它的声明:

enum Option<T> {
  None,
  Some(T),
}

例如,Vec::pop 方法,从堆栈向量中弹出最后一个元素,当堆栈向量中至少有一个元素时返回 Some 和元素,如果堆栈向量为空,则返回 None 。

现在,获取一个 Option 的值需要一个match结构。我们不能只是声明一个值已经被返回,并且像返回一个指针的语言一样使用它。那很好!程序员被迫考虑如何处理返回 None 的情况。如果在另一种语言中类似的代码会导致运行时错误,而在 Rust 中,将会阻止程序被编译:

let mut numbers = vec![21];
let maybe_number = numbers.pop(); // Option<i32>
println!("{}", maybe_number * 2); //  编译错误!

这个出现这个错误报告: error[E0369]: binary operation * cannot be applied to type std::option::Option<{integer}> 

必须使用 match 来判断是否有返回结果:

let mut numbers = vec![21];
let maybe_number = numbers.pop();
if let Some(my_number) = maybe_number {
  println!("{}", my_number * 2); // 现在正常工作了!
} // 我们还可以添加一个else代码块来处理None情况
亚林瓜子
 翻译得不错哦!

Result 类型

与 Option 类型相似,还有 Result 类型。Result 就像 Option 一样,但不仅仅是 Some 和 None,它可以是包含函数返回结果的 Ok,或者如果出现错误,它会有包含一个错误的 Err。这种错误也作为值返回的错误处理方式最好与 Go语言的方式进行比较。和 Rust 不一样,关键的区别在于 Go 语言中结果和 null-able 错误都会返回。这意味着忘记检查是否已经返回错误并使用结果,将导致运行时错误。

Rust 没有这个陷阱,因为像 Option 一样,必须先检查 Result 枚举的内容。因此,在发生错误时,不会误用结果。

亚林瓜子
 翻译得不错哦!

强制初始化

大多数其他语言允许程序员将变量的声明和初始化分开,这样的后果是,程序员有时往往会忘记初始化这样的变量,例如在分支中。虽然也有一些语言会在编译器中会终止编译(Java)或发出警告(C, C++),但这些并不会阻止程序员通过将变量初始化为 null 或零值而使得编译器不报错、在运行时引起崩溃或更糟糕的结果以及让程序做错误的处理。

对于未初始化的变量绑定,Rust 将拒绝编译,从而防止运行时错误:

let a: &str;
println!("{}", a.len()); // Compilation error!
a = "Hello";

这将报错:error[E0381]: use of possibly uninitialized variable: *a

当然,null 初始化技巧仍然是可用的,现在需要使用 Option 类型,如前所述,需要对 None 类型进行显式处理。

如果可以,一旦声明变量就初始化你的绑定:

let a = "hello";
println!("{}", a.len());
Tocy
 翻译得不错哦!

Traits

Traits(类似于其他语言中的接口)可以描述为可执行某些操作的类型的抽象定义。例如,Rust 标准库定义了一个 fmt::Display trait 表示它们自己为字符串的类型。Traits 让 Rust 看起来像静态类型语言,可以用作通用函数和类型的约束。

思考下面的函数:使用字符串表示将一段整数写入文件:

fn write_list(out: &mut fs::File, numbers: &[i32]) -> io::Result<()> {
  for num in numbers {
    writeln!(out, "{}", num)?;
  }
  Ok(())
}

简单吧? 但是如果是无符号整数呢?字符串呢?浮点数? 自定义类型? 我们需要为所有类型的类型创建同样一个函数!

亚林瓜子
 翻译得不错哦!

但是 Rust 可以使用一个类型参数为我们做到这一点:

fn write_list<W, T>(mut out: W, things: &[T]) -> io::Result<()>
  where W: io::Write,
        T: fmt::Display {
  for thing in things {
    writeln!(out, "{}", thing)?;
  }
  Ok(())
}

现在,此函数接受实现Display trait的任何类型。

我也可以使用W替换了一个类型参数&mut fs::File,并且必须实现 io::Write trait。 这使得更容易编写单元测试,因为不用使用fs api和临时文件,我们只是使用一个vector,因为它实现了io::Write。 请注意,W参数是所有而不是引用(&mut W),因为W的io::Write的实现是所有可变引用的类型的io::Write实现!

亚林瓜子
 翻译得不错哦!

引用

如果你以前用过C/C++,那你或许理解指针。引用(References),是一种可以通过其读写一块内存而不用关心其如何分配的东西。然而Rust中的引用和C中的指针不太一样,因为引用不能为null。

在我们了解引用存在的必要性以及工作模式之前,我想先解释一下所属(ownership)的概念。

xue777hua
 翻译得不错哦!

所属

Rust和C相同的是,其程序中的数据被存在heap或者stack中,而且没有垃圾回收机制。其不同点是,Rust规定了特定的内存管理方式:其子过程(subroutine)的所属者(owner)负责分配和释放子过程的内存。

这种内存管理模式防止了忘记释放内存、释放两次以及释放后再次使用内存的情况,进而消除了烦人的安全bug。这是Rust语言内置的机制,并且用这个替换了垃圾回收器,这是一种在编译期间安全管理内存的方式。

xue777hua
 翻译得不错哦!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
评论(7)
Ctrl/CMD+Enter

不错,学习。
最后一段的 perl6 是什么鬼
Rust 的类型系统非常现代,值得学习!

引用来自“sealin”的评论

最后一段的 perl6 是什么鬼
最后的 Perl 6 实际上是说存在这样一个类型系统:类型须要满足一定约束。这也是非常先进的特性,在函数式语言中早已出现了。实现这样的特性后,数组越界有可能在编译期就被发现,而编译期不能确定的数组访问,编译器会强制程序员进行边界检查。

事实上少数语言还有 dependent type(依赖类型),即某些函数的参数的类型可以依赖于另一个参数的取值,如 scanf 后面的参数依赖于第一个参数的取值,这可以使得编译器在 scanf("%d", some_int) 这样的错误代码上报错。
rust是一门安全的语言,减少bug,值得学习
还有篇不错,http://www.jianshu.com/p/a4bc33022aa3 [译]用Rust轻松搞定并发编程
到现在都没听说过rust能做什么
我觉得这些非常适合外包公司使用,各种强制。
顶部