1.3 Rust初体验
在本节内容中,你将初次体验Rust。我们首先要了解如何使用编译器,然后会快速编写一个程序。在接下来的章节中,我们会写一个完整的项目。
注意 要安装Rust,需要使用官方提供的安装器(installer)进行安装。请登录Rust官方网站并下载。
1.3.1 直通“Hello, world!”
大多数程序员在接触一门新的语言时,要做的第一件事就是学习如何在控制台上输出“Hello,world!”。接下来你也需要这样做。在遇到令人讨厌的语法错误之前,你要先验证所有环境是否已经准备就绪。
如果你使用的是Windows系统,在安装完Rust以后,请通过开始菜单打开命令提示符窗口。然后请执行以下命令:
C:\> cd %TMP%
如果你使用的是Linux或macOS系统,请打开一个终端窗口。打开后,请执行以下命令:
$ cd $TMP
从这里开始,所有操作系统的命令应该是相同的。如果你的Rust环境安装正确,那么执行以下3个命令,将会在屏幕上输出“Hello,world!”(以及一些其他输出)。
$ cargo new hello
$ cd hello
$ cargo run
这是在Windows上执行cmd.exe进入命令行提示符窗口以后,执行整个会话过程的示例:
C:\> cd %TMP%
C:\Users\Tim\AppData\Local\Temp\> cargo new hello
Created binary (application) 'hello' project
C:\Users\Tim\AppData\Local\Temp\> cd hello
C:\Users\Tim\AppData\Local\Temp\hello\> cargo run
Compiling hello v0.1.0 (file:///C:/Users/Tim/AppData/Local/Temp/hello)
Finished debug [unoptimized + debuginfo] target(s) in 0.32s
Running 'target\debug\hello.exe'
Hello, world!
类似地,在Linux或macOS上,你的控制台上显示的信息应该像下面这样:
$ cd $TMP
$ cargo new hello
Created binary (application) 'hello' package
$ cd hello
$ cargo run
Compiling hello v0.1.0 (/tsm/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.26s
Running 'target/debug/hello'
Hello, world!
如果你走到了这一步,那就太棒了!你已经运行了自己的第一段Rust代码,而且并不需要编写多少Rust代码。接下来,让我们来看看在整个过程中都发生了什么。
Rust的cargo工具既提供了一个构建系统,又提供了包管理器。这意味着cargo知道如何将Rust代码转换成可执行的二进制文件,同时能够管理项目依赖包的下载和编译的过程。
cargo new
会遵照标准模板创建一个项目。执行tree
命令就能清楚地看到在执行cargo new
之后默认的项目目录结构,以及创建出的那些文件:
$ tree hello
hello
├──Cargo.toml
└──src
└──main.rs
1 directory, 2 files
用cargo创建出来的所有Rust项目有着相同的结构。在项目的根目录中,名为Cargo.toml的文件描述了项目的元数据,例如项目的名称、项目的版本号及其依赖项。源代码保存在src目录中。Rust源文件使用.rs作为它的文件扩展名。要想查看cargo new
创建出来的那些文件,可以使用tree
命令。
接下来,你要执行的命令是cargo run
。这个操作对你来说很容易掌握,然而cargo实际上做的工作比你以为的要多得多。你要求cargo去运行此项目。在调用此命令时,还没有任何实际可执行的程序文件存在,它决定使用调试模式(debug mode)来为你编译代码,这样可以提供最大化的错误信息(error information)。碰巧的是,src/main.rs文件总是会包含一个“Hello,world!”作为初始代码。编译的结果是一个名为hello(或hello.exe)的文件。紧接着它会执行这个文件,并把执行结果输出到屏幕上。
执行cargo run
以后,项目还会增加一些新的文件。现在,在项目的根目录中会有一个Cargo.lock文件,还有一个target/目录。这两者都是由cargo管理的。因为它们都是编译过程中的产物,所以可以不予理会。Cargo.lock文件指定了所有依赖项的具体版本号,所以程序总会使用同样的方式,可靠地构建将来的程序版本,直到Cargo.toml的内容被修改才会改变这种构建的方式。
在执行cargo run
来编译项目以后,再次执行tree
命令来查看新的目录结构:
$ tree --dirsfirst hello
hello
├──src
│ └── main.rs
├──target
│ └── debug
│ ├──build
│ ├──deps
│ ├──examples
│ ├──native
│ └──hello
├──Cargo.lock
└──Cargo.toml
至此,所有步骤能正常运行了,很好!我们已经走捷径直通“Hello, world!”,接下来让我们走一条稍远点儿的路,再次到达这里。
1.3.2 第一个Rust程序
作为第一个Rust程序,我们想编写代码,用于输出以下文本信息:
Hello, world!
Grüß Gott!
ハロー・ワールド
在本书的Rust之旅中,你应该已经见过此输出内容的第一行了。另外的两行是为了展示出Rust的以下特点:易用的迭代和内置对Unicode的支持。与1.3.1节中的程序一样,在这个程序中我们会使用cargo。具体步骤如下。
(1)打开控制台窗口。
(2)如果是在Windows上,就执行cd %TMP%;如果在其他操作系统上,就执行cd $TMP。
(3)执行cargo new hello2命令,创建一个新项目。
(4)执行cd hello2命令,移动到此项目的根目录中。
(5)在一个文本编辑器中打开src/main.rs文件。
(6)用清单1.1中的内容替换该文件中的文本。
清单1.1的源代码参见ch1/ch1-hello2/src/hello2.rs。
清单1.1 用3种语言说“Hello,world!”
1 fn greet_world() {
2 println!("Hello, world!"); ⇽--- 这里的第一个感叹号表示这是一个宏,这个我们稍后会讨论。
3 let southern_germany = "Grüß Gott!"; ⇽--- Rust中的赋值,更恰当的说法叫作变量绑定,使用let关键字。
4 let japan = "ハロー・ワールド"; ⇽--- 对Unicode的支持,是“开箱即用”的。
5 let regions = [southern_germany, japan]; ⇽--- 数组字面量使用方括号。
6 for region in regions.iter() { ⇽--- 很多类型都有iter()方法,此方法会返回一个迭代器。
7 println!("{}", ®ion); ⇽--- 此处的和符号(&)表示“借用”region的值,用于只读的访问。
8 }
9 }
10
11 fn main() {
12 greet_world(); ⇽--- 调用一个函数。要注意紧跟在函数名后面的圆括号。
13 }
现在,代码更新了,只需在hello2/目录里执行cargo run
,你应该能看到这3个问候语(它们出现在cargo自己的一些输出的后面),如下所示:
$ cargo run
Compiling hello2 v0.1.0 (/path/to/ch1/ch1-hello2)
Finished dev [unoptimized + debuginfo] target(s) in 0.95s
Running 'target/debug/hello2'
Hello, world!
Grüß Gott!
ハロー・ワールド
让我们花点儿时间来谈谈清单1.1中一些有意思的语言元素。
在这个例子里,你可能最先注意到的就是,Rust中的字符串能够包含许多不同的字符。在Rust中,字符串能够确保是有效的UTF-8编码。这意味着你可以相对轻松地使用非英语的语言。
有一个字符看起来会有点儿奇怪,就是println后面的感叹号。如果你用Ruby编写过程序,可能就会习惯性地认为它表示一个破坏性的操作。然而在Rust中,它表示的是使用一个宏。就现在来讲,你可以把宏看作一类奇特的函数,其提供了避免“样板代码”(boilerplate code)的能力。对于本例中的println!
宏来说,实际上它在底层进行了大量的类型检测工作,所以才能把任意的数据类型输出到屏幕上。