《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)
Rust是近两年呼声比较高的一种新型开发语言,市场占有量并不大,但增长速度极为迅猛。
有人统计过,在计算机行业,平均每33.5天就有一种所谓的新型开发语言面世,这还不包括很多企业内部、项目内部的内置简易流程工具。然而大浪淘沙,如今仍然占据着市场地位的,仍然是耳熟能详的有限几种。
作为新来的搅局者,Rust到底值不值得学习并且在工作中应用呢?先说结论,这里粗略地把开发者分为初学者、小有经验的常规工程师和资深开发者三类。
对于初学者,Rust具有比较陡峭的学习曲线,虽然学习Rust能训练良好的编程习惯,从长远来看对提高学习者的开发素养极具价值。但短期的大量付出很容易让初学者心力交瘁。并且尽管官方文档并不欠缺,但学习资料对于初学者来说仍然远远不够,比较而言得不偿失。因此,建议初学者仍然使用久经验证的语言入门加入软件开发的大家庭,比如C、Java、Python、JavaScript都是很好的入门选择。
对于有一定经验的常规工程师,他们已经有了一段时间的开发工作实践,对于软件开发的现状、发展都已经形成了自己的世界观。如果感觉不是很喜欢这个行业,希望将来转行管理岗位或者产品岗位。那么当前应当做的是倾向业务领域,了解业务和技术的衔接和互动,完全不需要学习Rust。而如果醉心于技术,并从中获得了自己的乐趣,希望逐步提高自己的技术水平,那么Rust会是一个很好的桥梁,哪怕仅仅学习Rust而并不将其应用于工作,也能让开发者从中获取大量的有益习惯和软件底层经验,从而形成自己良好的代码风格。
对于资深工程师,即便并不从事底层系统级的开发工作,Rust也是一门很优秀的语言。它能弥补当前多种开发语言的不足,形成良好的开发哲学和思想导向,帮助开发者交付高质量的软件产品。因此,及早学习并应用Rust非常有价值。
为了说明这个结论,下面从创新和语言比较这两个角度,来说一说笔者对Rust的理解。
Rust是一种全面创新的语言
值不值得学笔者说了不算,我们要评估这门新语言的特点。这几年出现了不少有影响力的语言出现,但大多数都只是关键字或者小范围的语法创新,随后可能会有大量的特色库函数来丰富语言的功能。一个有经验的开发者,可能翻两天资料,就能快速掌握。
而Rust极具自身语言特点,是一种完全创新的语言,而不是简单的语法替换。简单地熟悉几个关键字、判断、循环等语法,远不足以掌握这门语言。
为了证明这一点,下面用Rust的“所有权”(Ownership)机制和“遮蔽”(Shadowing)机制来举例说明。以C++为例,请看下面这段代码:
#include using namespace std;
int main(){
string s1="hello";
string s2=s1;
cout << "s1=" << s1 << ",s2=" << s2 << endl;
return 0;
}
编译执行后,程序输出:
s1=hello,s2=hello
代码再简单不过,首先声明、赋值一个字符串变量s1,然后把变量s1赋值给变量s2,最后输出两者的值。对应地,我们看一个Rust的版本:
fn main(){
let s1=String::from("hello");
let s2=s1;
println!("s1={},s2={}",s1,s2);
}
除细小的语法差异外,看上去跟C++版本没有什么不同。然而在Rust中,这段代码连编译都无法通过,得益于rustc编译程序详细的输出,我们能看到很细致的错误提示:
2 | let s1=String::from("hello"); | -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait 3 | let s2=s1; | -- value moved here 4 | println!("s1={},s2={}",s1,s2); | ^^ value borrowed here after move
这个编译错误是指,上面的代码中,当变量s1赋值给s2之后,s1变量名所指向的内存所有权被“转移”(move)到了s2变量名之下。从此之后,s1变量名就无效了,不再指向任何一块内存。除非重新声明并为s1赋值(Rust中称为Shadow,“遮蔽”原有的s1),s1不能再被使用。
所有权机制可以有效防止内存泄露所导致的程序Bug,是Rust内存管理的核心理念。上面提到的所有权“转移”是所有权管理的重要特征之一。
“遮蔽”也是一个有趣的概念,Rust的处理方式跟很多我们熟悉的语言不同。
请看下面的C语言代码:
#include int main(){
int x = 5;
x = x+1;
printf("x=%d\n", x);
}
这又是一段很基本的代码。首先声明、赋值一个整数变量x,接着把x的值加1,再赋值回变量x。这是各种开发语言中都常见的用法。编译执行的输出结果为x=6。
下面来看Rust的版本:
fn main(){
let x=5;
x = x+1;
println!("x={}", x);
}
很不幸,这段代码同样无法编译通过,错误是:
error[E0384]: cannot assign twice to immutable variable `x` --> test-own1.rs:3:5 | 2 | let x=5; | - | | | first assignment to `x` | help: make this binding mutable: `mut x` 3 | x = x+1; | ^^^^^^^ cannot assign twice to immutable variable
编译器rustc这种“图示”型的输出信息让你排查错误更加方便。错误的原因在于,在Rust中,默认所有变量都是只读类型的,除非在变量声明的时候就注明为可变类型mut。因此,两次对于一个只读变量赋值导致编译错误。解决的办法是,要么注明变量为可读写,这样与C语言版本具有完全相同的意义:
let mut x=5;
要么用前面提到过的“遮蔽”机制:
fn main(){
let x=5;
let x = x+1;
println!("x={}", x);
}
注意,在上面的x=x+1这一行的开始再次使用let关键字,表示再次声明了变量x。
与大多数语言不允许重复声明变量不同,这个变量x跟第一次声明的变量x同名,并对其做出了“遮蔽”。之后除非再次遮蔽变量x,那么起作用的都将是本次新声明的x。
通过这两个例子可以看出,Rust是从理念上做出了大量创新的一种语言。如果只是像学习其他语言一样对比学习语法和关键字,无法真正掌握这门语言。这些融汇在语言中的理念才是Rust最宝贵的地方。注意,在这里“理念”可不是什么大而化之的套话,而是实际操作中很重要的原则。
很多语言的设计初衷是“简化”,在Rust中当然也有很多简化的地方,就像直接使用let关键字声明一个变量,而变量的类型可以通过赋值的操作从而推导出变量的类型。比如变量超出作用域,也会被自动回收。但Rust中也大量存在“复杂化”的操作,比如上面举例的所有权机制,再比如使用可读写变量需要额外标注mut。这些“复杂化”的部分,都基于“尽量在程序开发的早期,就将可能会出现问题的部分暴露出来,从而在设计中和编译时就解决掉”这样一个理念。
Rust和其他语言的总体比较
Java、C、Python在实际中都应用得很好,为什么人们还要设计出一个Rust?笔者当初也很困惑,我们首先将其和其他优秀语言做一下总体比较。
1. Rust与C++比较
Rust和C/C++相比肯定是稍显年轻,最初的开发者只有一位,就是Graydon Hoare,之后得到了Mozilla的赞助。Rust的语法与C++相似,它能提供更高的速度和更好的内存安全,不用自动垃圾回收,也无须手动释放。
在安全的内存管理方面,不少开发者把Rust当作一种更具有创新性的系统级语言,因为它不允许悬空指针或者空指针。
在外媒The Register的文章中写道:或许我们总是可以写出完美安全的C/C++代码,只是大多数情况下这不是一件容易的事情。因为这两种语言都太容易造成内存错误了,比如无效的栈和堆内存访问、内存泄露、不匹配的内存分配和反分配、未初始化的内存访问。
2. Rust与Java比较
对于开发者而言,完美的资源分配和良好的内存管理是Rust突出的优点。使用Rust可以轻易尝试各种类型新颖的复杂项目,之前由于Java语言的复杂性,用户不敢轻易尝试的都可以用Rust尝试。
3. Rust与Python比较
Rust超越Python的一个主要原因是性能。因为Rust是直接编译成机器代码的,所以在代码和计算机之间没有虚拟机或解释器。
与Python相比,另一个关键优势是Rust的线程和内存管理。虽然Rust不像Python那样有垃圾回收功能,但Rust中的编译器会强制检查无效的内存引用泄露和其他危险或不规则行为。
编译语言通常比解释语言要快。但是,使Rust处于不同水平的是,它几乎与C和C++一样快,但是没有开销。