For investors
股价:
5.36 美元 %For investors
股价:
5.36 美元 %认真做教育 专心促就业
如果你曾经编写过任何规模大小的程序,你可能会遇到各种错误。你在编码时产生的微小的错误会导致你的程序执行失败。程序越复杂,发生错误的概率越高!
为了修复和防止错误,有很多方法可供程序员使用。其中一个是在运行程序之前确定程序的正确性:静态类型。这种技术是设计编程语言的一部分,并且可以防止简单的错误,例如尝试使用字符串作为整数,或者比较类型不同的对象,例如 Car 和 Book 。
我的个人观点是,编程语言及其实现应该尽可能地捕获程序员所犯的错误,从而使得他们能构建更好更安全的软件。虽然静态类型使得语言更加复杂和难以学习,但它为程序员提供了一个安全的机制,我相信这是非常值得的。
Rust 语言实现了这样的静态类型系统,并提供了捕获错误的新方法,这在其他语言中运行时会导致崩溃。在这篇文章中,我将会讲解其中一些方法。
Null 返回
子程序由于遇到某种边界情况而无法返回结果并不罕见。想想一个例子:在数组中查找并不包含某项元素的索引,从空堆栈中弹出元素或在很小的数组上的通过索引访问元素。这些处理方式在不同语言之间差别很大。最常见的处理方式似乎是抛出异常或返回 null(或-1)。
如果你忘记检查 -1 或 null 或捕获异常,你的程序将会崩溃。然而,Rust 有一个确保在编译时捕获和处理错误的策略。
Option 类型
使用Rust的标准库 Option 来处理函数边界情况。这是一个枚举类型(如果你熟悉C语言的话,有点像一个联合体)包含有两个可能的值。这是它的声明:
enum Option {
None,
Some(T),
}
例如,Vec::pop 方法,从堆栈向量中弹出最后一个元素,当堆栈向量中至少有一个元素时返回 Some 和元素,如果堆栈向量为空,则返回 None 。
现在,获取一个 Option 的值需要一个 match 结构。我们不能只是声明一个值已经被返回,并且像返回一个指针的语言一样使用它。那很好!程序员被迫考虑如何处理返回 None 的情况。如果在另一种语言中类似的代码会导致运行时错误,而在 Rust 中,将会阻止程序被编译:
let mut numbers = vec![21];
let maybe_number = numbers.pop(); // Option
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());
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(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)的概念。
所属
Rust 和C相同的是,其程序中的数据被存在heap或者stack中,而且没有垃圾回收机制。其不同点是,Rust规定了特定的内存管理方式:其子过程(subroutine)的所属者(owner)负责分配和释放子过程的内存。
这种内存管理模式防止了忘记释放内存、释放两次以及释放后再次使用内存的情况,进而消除了烦人的安全bug。这是Rust语言内置的机制,并且用这个替换了垃圾回收器,这是一种在编译期间安全管理内存的方式。
IT作为目前有前景、有钱景的行业,无数的人加入了这个大军当中。达内时代科技集团致力于培养几大方向中高端软件人才课程与少儿教育课程。合肥计算机培训助你一臂之力,更多免费训练营让你从零起步。
【免责声明】本文系本网编辑部分转载,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与管理员联系,我们会予以更改或删除相关文章,以保证您的权益!