目录

Rust学习笔记(1)

Rust是开源大佬Mozilla在2014年发布的一款面向系统级应用和底层应用开发的程序设计语言。在这之前,Google在2009年发布了Go语言,并且取得了不小的成功,比如Docker,这款极为强大且好用的容器引擎是使用Go构建的。不过Rust在发布的三年后也已经收获了一批拥趸,第一个面向很多用户的项目,Firefox Quantum版本中的CSS引擎(是用Rust写的)也即将发布了。我已经看过不少PL大师和一些搞编译器部分的大佬在狂吹Rust,Rust的社区也日渐壮大。

作为目标都是干掉C/C++,面向底层应用开发的程序语言,Go和Rust其实无形中有一种竞争关系(BTW,浏览器上,Google也一直在和Mozilla竞争)。7月的时候我也看了一下Go,看的很少,被语法恶心到了一下,可能是因为看得比较少的原因,之后应该会再看一下;但是从我前天晚上点开Rust开始,我却像上瘾一样被这门语言深深的吸引了,它的内部一些设计让我看到了Rust的优越性:

  • 面向底层开发,高效的C绑定与极小运行时
  • 零开销抽象,独特却先进的内存管理模型
  • 线程无数据竞争,数据的所有权控制
  • 极其前沿的语言特性,对FP的出色支持

等等等等。在看了这么一段时间之后,我已经深深地爱上了它,并且将把这门语言作为我C++的后备和全新的主力。

说了这么多,写点简单的东西作为笔记吧。

一、安装与配置

安装与配置十分简单。这里就要吹一波,Rust的工具链十分好用,你不需要任何IDE,一个文本编辑器就够了。

我的配置环境很简单:

  • Elementary OS 0.4.1 based on Ubuntu 16.04.1
  • VS Code Latest Stable

那安装步骤实际上可以说是傻瓜式了:

  1. 在终端中运行:

    1
    
    curl https://sh.rustup.rs -sSf | sh
    

    就行了。Rust的安装程序会把你需要的自动配置好。之后你重启一下shell就行。

  2. 验证安装:运行一下rustc --versioncargo --version。这两步如果都返回一个带版本号的提示信息就代表配置完成了。如果没有,找到你的安装路径,一般是~/.cargo/bin,加进环境变量即可。

安装之后,来到VS Code,有官方的插件和一个用户写的。我用的是那个用户写的,安装然后Reload就行。可以用ext install vscode-rust

这个插件的工作方式有两种:

  • Legacy:这个模式下是插件配合,Stable Channel下可以正常工作,但是作者说这个模式做的虽然好,但是RLS模式更好,可能会废弃,所以现在可以用,也比较省心,但是未来要记得更换。
  • RLS(Rust Language Server):Rust官方写了Language Server,也就是有了IDE级别的辅助开发能力,我更推荐这个模式。不过现在RLS还是预览版,如果要使用需要自行切换工具链。

我用了RLS模式。具体安装流程如下:

  1. 从nightly channel安装

    1
    
    rustup install nightly
    
  2. 安装依赖&Racer

    1
    2
    3
    4
    
    rustup component add rls-preview --toolchain  nightly
    rustup component add rust-analysis  --toolchain nightly
    rustup component add rust-src --toolchain  nightly
    cargo install racer
    
  3. VS Code配置

    打开用户配置文件,添加:

    1
    2
    3
    4
    
    "rust.mode": "rls",
        "rust.rls": {
            "useRustfmt": true
    }
    

这就配置好了。其他编辑器请自己找配置教程。

还要注意,这里配置之后,默认的工具链还是在stable下,我们可以这么切换工具链:rustup default nightly。这样整个工具链会整体切换到nightly下面,包括cargo。如果你需要换回stable,只需要rustup default stable即可。另外,更新的时候使用rustup self updaterustup update,所有版本都会获得更新。

注意,nightly顾名思义,每夜版,是最前沿的开发版,可能会出现rls-preview更新跟不上等等问题,假如遇到了,可以考虑切换到beta channel,具体方式同上。如果发现VS Code无法使用beta channel下面的rls分析的话,把配置文件中"rust.rustup":{"nightlyToolChain"}后面的字段改成beta就行。

简单一提几个命令:

  • 编译一个.rs文件:rustc hello.rs
  • 新建一个库项目:cargo new helloworld
  • 新建一个应用程序项目:cargo new helloworld --bin
  • cargo编译:cargo build
  • cargo编译并运行:cargo run
  • cargo编译发行版:cargo build --release
  • cargo生成文档:cargo doc
  • cargo测试:cargo test

差不多就这些了,更多的请自己查文档

二、简单语法

Rust的语法很容易理解,不过也有和传统语言不同的地方。

首先是程序主体,main()函数仍然是程序入口:

1
2
3
fn main(){
    // Your code here...
}

当然,这个是程序项目,库项目不需要这个东西。

变量的声明需要讲讲:

1
2
3
4
let x = 15; // 最基本的变量声明,变量不可改,类型自动推导:x -> i32
let mut y = 16; // y是一个可变变量,类型也被自动推导:y -> i32
let z: f32 = 1.234; // z是不可变的f32变量,冒号后面是类型注解,这样相当于告诉编译器z是f32类型
const MAX_SIZE: i32 = 155; // 常量,声明时必须有类型注解,绝对不允许修改

简单吧?要注意的是,Rust用的是Hindley-Milner类型系统,这是一个十分现代的类型系统,如果用过Haskell的话会对这种类型系统十分熟悉。

Hindley-Milner类型系统:

简单来说,HM类型系统可以对你的变量类型进行自动推导,这样你就不用在每一次指定你的变量类型;同时,由于它强大的类型推导能力,当你进行函数应用的时候,会自动推导出你的程序每一步的函数调用、返回赋值等等类型是否一致,能在静态检查时扼杀大部分的调用错误。

这里要强调一下mut关键字。Rust的变量是FP+IP的混合系统,在FP系统中,变量统一是不可变的,这很好理解,因为在函数式下变量和函数应用过程没有任何关系(参照我曾经写过的这篇文章),所以变量自然是不可变的。但是在我们的应用过程中很自然的需要一些可变变量,则我们使用mut来声明变量就可以让它变得可变。

类型注解功能只是显式指出类型是什么,如果注解和编译器推导的有问题仍然编译不过。

变量声明还有其他内容需要讲,这一部分将在数据类型中有涉及。


数据类型之后讲,这一部分和很多内容有关。


流程控制下,Rust综合了很多语言的流程控制顺序。具体来说,有以下三种:

  • loop循环

    1
    2
    3
    4
    
    loop {
        // 这个循环是确定的无限循环,你只能使用break退出
      break;
    }
    
  • while循环

    1
    2
    3
    
    while count != 0 {
        count -= 1; // Rust没有--与++
    } // 这就是我们熟悉的while循环
    
  • for循环

    1
    2
    3
    
    for i in v.iter() {
        // do something with i
    }
    

for循环没看懂?有Python基础的程序员应该能立即看懂,但是只有Java或者C/C++背景的程序员可能有点难理解。简而言之,只要in后面的对象是可迭代的,都能使用for循环去遍历,可以看看Python或者Rust标准库文档里面的示例深入理解一下。

continuebreak这两个关键字仍然可用。


我们考虑如下一段C++代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
if (a == 15)
{
    return 16;
}
else if (a == 16)
{
    return 17;
}
else
{
    return 0;
}

根据a的不同值,我们得到不同的答案。当然,我们也可以使用switch。在Rust中,我们将见到更加强力的一种语法:模式匹配。

1
2
3
4
5
match a {
    15 => 16,
    16 => 17,
    _ => 0,
};

模式匹配的启动由一个match运算符引起,之后是一个表达式,可以是布尔表达式如if a == 25,或者就是单纯的一个变量,如a。大括号内部是匹配主体,由多个分支(arm)构成。

表达式与语句:

  • 语句(Statements):执行一些操作但不返回值的指令
  • 表达式(Expressions):计算并产生一个值

分支遵循如下格式:

1
pattren => if_matched,

前面是匹配成功的模式,后面是匹配成功后执行的表达式。比如,上面的15 => 16意思就是:

1
2
3
if a == 15 {
    16 // 这一行涉及到语句块的返回,会在之后的函数部分讲到。
}

这个匹配成功的表达式(即if_matched)可以使用大括号括起来的一组表达式或者语句构成。

_的意思是通配,也就是所有内容都可以匹配上。那既然都可以匹配上,怎么保证15不会匹配到_上面呢?很简单,match带来的模式匹配由上到下,也就是越靠前的优先级越高,我们匹配时从上往下匹配,直到匹配上为止。同时,Rust也会要求你必须穷尽匹配,比如上面的代码如果这么修改:

1
2
3
4
match a {
    15 => 16,
    16 => 17,
};

编译器会报错,因为匹配到除了15和16以外的值是没有对应分支的,这样这个匹配是无法穷尽的。所以,一个match运算符中的模式匹配必须穷举所有可能的模式。到这你应该能体会到_的用处了。

match所能带给我们的方便和优越性都是来自HM类型系统的功劳。或许,现在你还不能看出它的强大之处,等我们之后讲解了Rust的枚举之后你再品味,就能体会到它的强大了。


简单语法的最后,提一下怎么向控制台打印:

1
2
3
4
5
println!("这一条会打印新的一行"); // 字符串可以是UTF-8的合法字符
print!("这一条会打印,但不会换行~")
println!("后面这个东西{}" ,"会被参数内容替代~"); // {}是模板字符串的语法,这里默认是参数打印的占位符
let x = 5;
print!("下面这个是Debug打印的占位符:{:?}", x); // {:?}在Debug的时候打印变量信息很好用,更多细节之后讲

打印的时候出现了三个新的点:宏与函数,模板字符串和Debug模式。第一个第三个都之后讲,这里重点讲模板字符串。

模板字符串是在JavaScript和Python通用的一种字符串格式化的语法。考虑如下C++代码:

1
printf("%d %s\n", num, str);

这样读起来其实很难受,因为每一个标识符代表的意义不同,无论是人类去读或者编译器去parse都不容易。模板字符串则足够简单:

1
2
3
let x = 15;
let y = "你好呀~";
println!("数字x内容是:{},字符串y内容是:{}", x, y);

我们只需要处理{}即可。当然,所有的参数都在这里面,比如我们可以指定顺序:

1
println!("字符串y内容是:{1},数字x的内容是:{0}", x, y);

至于模板字符串的格式化参数,请查阅库文档。

下集预告

讲的好少啊…….

一篇能写下的东西也不多,其实就是官方的实例的精简版,哈哈。

下期会讲一下Rust设计里面我觉得最优秀的两个地方:所有权(Ownership)系统和Option。主题应该是函数与变量和数据类型了。

我的主要参考资料是Rust 程序设计语言中文第二版,感谢翻译者和作者的辛勤工作!

See you soon~

Changelog:

2017.10.30 V1.0:最初版

2017.10.30 V1.1:添加必要链接,修正部分描述和结构,有关RLS部分修正