Rust 编程心得分享(一)
前言
了解 Rust 已经一段时间了。最初是因为由 Node.js 之父 Ryan Dahl 编写的项目 Deno 知道到的 Rust 。
Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.
然后又了解到 WebAssembly(wasm) :WebAssembly是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它设计的目的不是为了手写代码而是为诸如C、C++和Rust等低级源语言提供一个高效的编译目标。
wait a minutes,这是什么语言,没怎么听说就俨然和 C/C++ 进入了同一梯队的样子。
没办法了,如果你在很多个地方遇到同一个东西的时候,你就会意识到:是时候跟它好好认识一下了 : )
与 Rust 第一次约会
有很多语言或者框架,在你和它们初次见面的时候总会磕磕盼盼。比如 某加加,光是编译器就不知道要选哪个 。但是 Rust 的安装十分顺滑,在一个平静的午后,我分别在 Windows 和 MacOS 上安装了 rustup ,一路下来畅通无阻,这点很增加程序员幸福感,我甚至想点一杯卡布奇诺了。
Cargo
Cargo 是 Rust 的构建系统和包管理工具。类似于npm 于NodeJS, maven 于 Java。Cargo 还自带了测试和文档生成。所以这个家伙要好好认识,以后少不了跟他打交道。另外 Rust 的编译器是十分严格的,对于新手来说通过编译就已经是一件不容易的事了。对此可以使用 cargo c 乖乖地接受编译器的指导(调教):
# Analyze the current package and report errors, but don't build object files
cargo check
# or
cargo c
check 命令会分析当前项目是否能通过编译,但是不对他做构建工作。 多用 check 而不是 build。这会加快你的开发工作流程。具体如下:
| use case | build performance | check performance | speedup |
|---|---|---|---|
| initial compile | 11s | 5.6s | 1.96x |
| second compile (no changes) | 3s | 1.9s | 1.57x |
| third compile with small change | 5.8s | 3s | 1.93x |
表格数据来源于:doc.rust-lang.org/edition-gui…
HelloWorld!
fn main() {
println!("Hello World!");
}
见到 Rust 的代码,你会觉得 Rust 作者真的惜字如金,比如关键字 fn 对比 JavaScript 的 function 少了6个字符,所以在 js 中很多人之所以选择 () => {}, 除了穿透this之外,也有人是单纯的不想写这个长长的 function 关键字(比如我🙃)。
还有 public 写为 pub , 少了3个字符。在 Rust 中,外部模块想要使用内部的方法时,必须要声明为 pub 。
可见常用的关键字都非常的短,这个理念就像在游戏中,物品栏的1、2位置都是放各种加血回蓝消耗道具一样。
Rust 关键字:
as、break、const、continue、crate、else、enum、extern、false、fn、for、if、impl、in、let、loop、match、mod、move、mut、pub、ref、return、self、Self、static、struct、super、trait、true、type、unsafe、use、where、while
edition 2018 中新增关键字:
async、await、dyn
此外,官方还专门说明了一些尚未使用,但在未来中可能会使用的关键字 ,以防止版本的升级对程序带来破坏性的修改
所有保留的关键字:
abstract、become、box、do、final、macro、override、priv、typeof、unsized、virtual、yield
edition 2018 中添加的保留关键字:
try
类比一下一些关键字可能比较好理解:
use:类似于import,还支持类似解构赋值的写法;loop:类似于while(true);trait:类似于一些编程语言的interface或者abstract class;struct:类似于 C++ 的class或者 js 的prototype;impl:类似于implement,实际很常用,可用于给struct声明方法,和实现一些trait等;type:类似于TypeScript的type,所以不能用来做变量名 : (mod:类似于namespace;
所有权 OwnerShip
这个概念是 Rust 独有的。可能大部分人对于内存的操作只知道指针,因为 C 语言诞生于 1972 年,而 Rust 诞生于 2015 年。晚了整整43年,按辈分的话, Rust 可能要叫 C 一声爷爷。
对于内存,不同的语言有不同的管理方式:
- 垃圾收集器(GC):在程序运行时,会不断地寻找不再使用的内存,缺点是需要运行时,还有额外的性能开销。代表语言:Java 、JavaScript、Php、C#、Go、Python、Swift
- 手动分配内存:比如 C 通过
malloc来分配内存,然后通过free释放内存。这种做法把 100% 的控制权都交给了程序员,但是谁能保证每个内存都能合理地分配和释放呢?很多程序甚至是操作系统,它们的很多 bug 都是由于不正常地使用指针而导致的,所以谁也不能保证。代表语言:C、C++
而 Rust 的 OwnerShip 正是解决内存管理的另一种方案。通过强大的编译器,在编译期通过插入清理代码的方式,实现精准的清理,把一些 bug 扼杀在了摇篮时期。
如何理解 OwnerShip?
在鄙人看来,所有权更符合我们人的直觉。
打个比方,我去小卖部买了一瓶饮料,通过扫码支付把我一部分余额的所有权转给了店家,然后店家饮料的所有权转给了我。
店家查看已经被我拿走的饮料的时候,就会发现自己查看不了,因为所有权已经在我这了,而我买了饮料已经离开了小卖部了。
我们用代码描述一下这个过程:
let my_money = String::from("三块钱");
let my_drinks;
/* 小卖部的生命周期 */ {
let drinks = String::from("饮料");
let shop_owner_money = my_money;
my_drinks = drinks;
println!("shop owner money: {}", shop_owner_money);
println!("shop owner drinks: {}", drinks);
}
println!("my money: {}", my_money);
println!("my drinks: {}", my_drinks);
这里埋了个伏笔,为什么
my_money和drinks要使用String::from来声明?这里使用这个构造只是为了声明一个生命周期为当前生命周期的变量,因为这里只是讲所有权,所以先忽略这个吧 :)
let my_money = String::from("三块钱");
-------- move occurs because `my_money` has type `String`, which does not implement the `Copy` trait
let shop_owner_money = my_money;
-------- value moved here
println!("my money: {}", my_money);
^^^^^^^^ value borrowed here after move
这里 my_money 已经将我钱的所有权给了 shop_owner_money 。所以,当我买完饮料之后,再次去使用我的钱的时候,就会发生错误。
借用与可变借用
借用可以理解为:我可以借给你,但是所有权还是我的,你不要乱用!
可变借用可以理解为:我可以借给你,你也可以用。
理解了基本的概念之后,很多编译报错也就能理解了。
比如:
let mut myphone = String::from("华为");
let temp = &myphone;
myphone.push_str(" mate 40");
println!("{}", temp);
这里定义了一个可变变量 myphone,然后 myphone 作为不可用借用赋给了 temp。当我们调用 myphone.push_str 时,内部发生了一个可变借用,进入 push_str 的定义,如下方代码所示,self 是为 &mut。
pub fn push_str(&mut self, string: &str) {
self.vec.extend_from_slice(string.as_bytes())
}
但是这时其实并没有错误,真正错误的是第四行的 println!("{}", temp) ,此时的执行 cargo c 的报错为:
error[E0502]: cannot borrow `myphone` as mutable because it is also borrowed as immutable
...
let temp = &myphone;
-------- immutable borrow occurs here
myphone.push_str(" mate 40");
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
println!("{}", temp);
---- immutable borrow later used here
根据报错:不能借用 myphone 作为可变借用,因为他也被借为不可变借用
所以错误为我们调用 push_str 时,编译器早就盯好了我们,只要我们一用 temp 就给我们报错。
打个比方可以理解为:我的华为手机给你借用了,然后我把它刷成
IOS系统,你后面再用这台手机的时候,肯定人都傻了,所以这样是不行的。 但是这里好像只是把"华为"变成了"华为 mate 40"看似人畜无害,甚至有点赚?!但是你怎么能保证他不 push 一个
"\riphone 4"呢?(doge
那么我们该如何处理呢?
我们既然不可变借用会报错,那我们把 &myphone 改为 &mut myphone 应该可以吧~
error[E0499]: cannot borrow `myphone` as mutable more than once at a time
...
let temp = &mut myphone;
------------ first mutable borrow occurs here
myphone.push_str(" mate 40");
^^^^^^^ second mutable borrow occurs here
println!("{}", temp);
---- first borrow later used here
好家伙,还是报错了。
根据提示,我们可以很清楚的知道,我们不能一次借用可变借用超过一个。
这个应该很容易理解。
打个比方吧,上学的时候,你的橡皮擦肯定有借给别人过,但是可能别人把从你这借走的橡皮擦再去借给别人,然后别人又借给另外的人,最后你的橡皮擦,恐怕是再也见不到你了。
可能 Rust 作者从小就深知这个道理,所以才能想出这样的设计吧。: ^ )
经过编译器指导(调教)后的结果:
let mut myphone = String::from("华为");
let temp = &mut myphone;
temp.push_str(" mate 40");
println!("{}", temp);
总结
篇幅有限,这篇文章就到这了吧。
不得不说,随着时代的发展,很多事情都朝着符合本能的认知的方向去发展,所有权(OwnerShip)这个概念就是更加地容易理解。(起码我是这么认为的,OwnerShip 万岁)
在我看来,Rust 有着出色的包管理器和构建工具 Cargo,简洁如 Python 的语法,性能堪比 C/C++,现代化的配置文件格式 Toml,并且今年(2021)年初成立了 Rust 基金会(我们的华为也是创始白金成员),并将于年底推出 edition 2021,还是区块链的热门语言,你还不了解一下吗?
Rust 还是一门新语言,需要大家一起投入才能更好的发展(能拉一个入坑算一个)
以上为我个人总结的一些心得,如果对于我的内容有说明疑问和指正,欢迎评论区留言。