Hello World
普若哥们儿
Hello World
使用 rustc
任意文本编辑器编写:
fn main() {
println!("Hello, world!");
}
用 rustc 工具编译:
rustc hello_world.rs
得到 hello_workd.exe,运行:
hello_world
输出:
Hello, world!
使用 cargo
cargo 能够生成具有规范目录结构的项目,就像 maven 那样。
创建新项目
我们将在新的 Rust 开发环境中编写一个小应用。首先用 Cargo 创建一个新项目。在您的终端中执行:
cargo new hello_rust
这会生成一个名为 hello_rust 的新目录,其中包含以下文件:
hello_rust
|- Cargo.toml
|- src
|- main.rs
Cargo.toml为 Rust 的清单文件。其中包含了项目的元数据和依赖库。src/main.rs为编写应用代码的地方。
进入新创建的目录 hello_rust ,执行下面的命令来运行此程序:
cargo run
您应该会在终端中看到如下内容:
$ cargo run
Compiling hello_rust v0.1.0 (/Users/ag_dubs/rust/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 1.34s
Running `target/debug/hello_rust`
Hello, world!
添加依赖
现在我们来为应用添加依赖。您可以在 crates.io,即 Rust 包的仓库中找到所有类别的库。在 Rust 中,我们通常把包称作“crates”。
在本项目中,我们使用了名为 ferris-says 的库。
我们在 Cargo.toml 文件中添加以下信息:
[dependencies]
ferris-says = "0.3.1"
接着运行:
cargo build
之后 Cargo 就会自动安装该依赖。
%CARGO_HOME%/registry 是存放第三方包的地方。
第三方包 ferris-says 被下载到 %CARGO_HOME%\registry\src\index.crates.io-6f17d22bba15001f\ferris-says-0.3.1 目录下,其中有 ferris-says 的源文件。文件 %CARGO_HOME%\registry\cache\index.crates.io-6f17d22bba15001f\ferris-says-0.3.1.crate 是 ferris-says 包的缓存文件,这是一个压缩文件,用 7zip 等工具可以解压缩。
类似 nodejs,运行此命令会创建一个新文件 Cargo.lock,该文件记录了本地所用依赖库的精确版本。
要使用该依赖库,我们可以打开 main.rs,删除其中所有的内容,然后在其中添加下面这行代码:
use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};
fn main() {
let stdout = stdout();
let message = String::from("Hello fellow Rustaceans!");
let width = message.chars().count();
let mut writer = BufWriter::new(stdout.lock());
say(&message, width, &mut writer).unwrap();
}
我们可以输入以下命令来运行此应用:
cargo run
可以看到程序输出:
__________________________
< Hello fellow Rustaceans! >
--------------------------
\
\
_~^~^~_
\) / o o \ (/
'_ - _'
/ '-----' \
注释
注释对任何程序都不可缺少,同样 Rust 支持几种不同的注释方式。
- 普通注释,其内容将被编译器忽略掉:
// 单行注释,注释内容直到行尾/* 块注释,注释内容一直到结束分隔符 */
- 文档注释,其内容将被解析成 HTML 帮助文档:
/// 为接下来的项生成帮助文档//! 为注释所属于的项(译注:如 crate、模块或函数)生成帮助文档
格式化输出
这部分内容不属于 Rust 的语法范畴,而是 Rust 标准库中的内容。先介绍这些内容是为了提早能够练习编写程序,输出一些信息,以验证程序的逻辑,更好地理解 Rust 语法。
这部分内容有些概念在后面的章节才能介绍,这里只要能够依葫芦画瓢参照例子输出变量内容即可。
打印输出操作由标准库 std::fmt 里面所定义的一系列功能相似的 宏 来处理,包括:
format!:将格式化文本写到字符串。print!:与format!类似,但将文本输出到控制台(io::stdout)。println!: 与print!类似,但输出结果追加一个换行符。eprint!:与print!类似,但将文本输出到标准错误(io::stderr)。eprintln!:与eprint!类似,但输出结果追加一个换行符。
宏类似于函数,这些宏都以相同的做法解析文本。std::fmt - Rust (rust-lang.org) 对格式控制语法有详细的介绍。
格式参数
在 Rust 中,格式化参数是通过一系列占位符和相应的参数值来实现的。让我们先看一个简单的例子:
println!("{}天有{}小时", 1, 24);
这里,字符串 "{}天有{}小时" 可以被视为一个模板,模板中的 {} 称为格式参数。从第二个参数开始,后续所有参数构成了被格式化的参数列表——在上述例子中,1 和 24 是参数列表的成员。参数列表的长度没有限制:它可以包含任意数量的参数,而参数在列表中的索引从 0 开始。
一个格式参数的完整形式是 {which:how},其中的 which 和 how 都是可选的。因此,{} 表示既没有指定 which 也没有指定 how,即使用默认设置。
which 用于指定要格式化的特定参数,可以通过索引下标或者名称来选择。如果没有指定 which,则默认按照参数列表的顺序,从 0 到 n 自动选择参数。例如:
println!("{0} {1} {} {}", 66, 77);
// 等同于
println!("{0} {1} {2} {3}", 66, 77, 66, 77);
可以使用命名参数来个性化参数列表,但是如果同时使用索引和命名参数,那么命名参数必须放在最后。下面的例子演示了使用命名参数:
println!("{days}天有{hours}小时", days=1, hours=24);
至于 how,它定义了参数的格式化方式,例如对齐方式、浮点数的精度、数值基数等。若 how 部分存在,那么 : 是必须的。
文本类型的格式化
在 Rust 中,对字符串相关类型(如 String 或 &str)进行格式化时,how 部分的格式可能包括以下几个方面:
- fill(可选):填充字符,用于当内容的宽度小于所需的最小宽度时进行填充,默认是空格。
- align(可选):对齐方式,其中 < 代表左对齐,> 代表右对齐,^ 代表居中对齐,默认是左对齐。
- width(可选):指定最小宽度,如果内容的长度小于此宽度,将根据设置的对齐方式用填充字符补充。
- precision(可选,通常用于数字和字符串):对于字符串,它指定了最大输出宽度。如果字符串的长度超出这个值,会根据最大宽度截断字符串。
在使用对齐、填充、宽度和精度这些选项时,必须遵循它们的特定顺序。一个格式化占位符的典型结构如下所示:
{:[fill][align][width][.precision]}
重要的是要注意,fill 字符位于 align 字符之前,且两者之间不应有空格或其他分隔符。如果省略了 align 字符,默认会使用右对齐,并且填充字符默认是空格。
以下是一个展示这些元素如何被使用的简单示例:
println!("{:>8}", "foo"); // 输出 " foo",默认使用空格填充以及右对齐
println!("{:*>8}", "foo"); // 输出 "*****foo",使用 '*' 填充并右对齐
println!("{:*<8}", "foo"); // 输出 "foo*****",使用 '*' 填充并左对齐
println!("{:*^8}", "foo"); // 输出 "**foo***",使用 '*' 填充并居中对齐
数值类型的格式化
对于数值类型的格式化,Rust 采用的语法类似于文本类型,但具体内容会根据数值的特性有所不同。以下是数值类型格式化语法的概括:
{:[fill][align][sign][#][0][width][.precision][type]}
fill:(可选)一个填充字符,用于当文本表示的字符数不足宽度(width)时进行填充。align:(可选)对齐标志,<表示左对齐,^表示居中对齐,>表示右对齐(默认)。如果设置了0标志来填充零,则默认为右对齐。sign:(可选)符号,可用的选项有+或-或空格。+会强制为正数显示加号,负数显示减号;-只有负数时显示符号(这是默认行为);空格会在正数前面留一个空格。#:(可选)替换标志,用于对不同类型的格式增加特殊前缀,例如对于十六进制加0x,八进制加0o。0:(可选)用于在数值的左边填充 0,直到达到指定宽度时停止。width:(可选)指明最小宽度,如果数值的字符串表示不足这个宽度,将会使用 fill 进行填充。.precision:(可选)对于整数表示最小位数;对于浮点数表示小数点后的位数;对于字符串表示最大宽度。type:指定数值的进制或者是格式化的类型。例如:b表示二进制,x表示小写十六进制,X表示大写十六进制,o表示八进制,e/E表示指数形式(科学计数法),p表示指针地址。
以下是一些常见的数值格式化指令:
| 格式说明符 | 功能说明 |
{} | 使用 Display 类型进行默认格式化 |
{:b} | 以二进制形式格式化整数 |
{:o} | 以八进制形式格式化整数 |
{:x} | 以十六进制形式格式化整数(小写字母) |
{:X} | 以十六进制形式格式化整数(大写字母) |
{:e} | 以科学计数法(小写 e)格式化浮点数 |
{:E} | 以科学计数法(大写 E)格式化浮点数 |
{:?} | 使用 Debug Trait 进行格式化 |
{:width$} | 设定最小宽度,未达到宽度时使用空格填充 |
{:0width$} | 设定最小宽度,未达到宽度时使用 0 填充 |
{:width$.*precision$} | 一起设置最小宽度和精度,.precision$ 部分用来格式化浮点数 |
{:<width$} | 左对齐,使用空格填充到指定宽度 |
{:^width$} | 居中对齐,使用空格填充到指定宽度 |
{:>width$} | 右对齐,使用空格填充到指定宽度 |
{:+} | 显示数值的正负号 |
{: } | 对于正数留空格,对于负数显示负号 |
{:#} | 实现数值的替代格式(例如在十六进制前加 0x) |
注意:并不是所有的组合都有效,某些说明符仅对某些类型的数值有效。例如,precision 通常不适用于整数。需要注意的是,整数类型(例如 i32, u32 等)不支持使用 .precision 设置精度,该设置通常用于浮点数。
以下是一些数值类型格式化的示例:
fn main() {
// 格式化整数
println!("{:04}", 42); // 输出:0042
println!("{:+}", 42); // 输出:+42
println!("{:#x}", 255); // 输出:0xff
println!("{:#b}", 5); // 输出:0b101
println!("{:0>5}", 14); // 输出:00014
// 格式化浮点数
println!("{:.*}", 2, 1.234567); // 输出:1.23
println!("{:+.2}", 3.141592); // 输出:+3.14
println!("{:.2}", 3.141592); // 输出:3.14
println!("{:10.4}", 1234.56); // 输出:" 1234.5600",宽度为10,小数点后4位
println!("{:0>10.4}", 1234.56); // 输出:"0001234.5600",填充0,宽度为10,小数点后4位
// 格式化时使用特定的填充字符
println!("{:*>10}", 42); // 输出:********42
println!("{:.*}", 2, 1.234567); // 输出:1.23
}
参数化宽度和精度
格式化字符串时可以动态指定 width 和 precision。这可以通过命名参数或位置参数来实现,并且需要在参数名后加上 $ 符号作为后缀。例如:
let my_string = "Rust";
println!("{:>width$}", my_string, width = 10); // 命名参数指定宽度
println!("{:>1$}", my_string, 10); // 位置参数指定宽度
在这里,width$ 或 1$ 表明 width 的值将被取自相对应的参数。在上面的代码中,my_string 将被右对齐并打印在一个至少 10 字符宽的字段中。
打印内存地址
当涉及到打印一个内存地址时,对于引用、Box、以及其他实现了 Pointer trait 的类型,{:p} 格式说明符可用于输出这些类型的内存地址,这个特性通常用于调试和研究目的。下面的代码展示了如何打印一个变量的内存地址:
fn main() {
let my_var = 10;
println!("The memory address of my_var is: {:p}", &my_var);
}
在这个例子中,操作 &my_var 获取了变量 my_var 的引用,其类型是 &i32。输出的地址是 my_var 储存值的内存地址。请注意,程序每次运行时,或者在不同的系统上,输出的具体内存地址可能会有所不同。这是因为操作系统根据当前的内存使用情况、地址空间布局随机化等多种因素来分配变量的具体内存位置。
格式符号和特性
在 Rust 中,每个格式符号背后其实对应着一个特性(Trait),其中最经常用到的是 Display 和 Debug。目前,您不必深入了解特性的具体含义;可以简单地将其视作类似于接口的功能。
| 格式符号 | 特性 | 说明 |
{} | Display | 用于用户友好的输出,通常用于打印给终端用户看的信息。 |
{:?} | Debug | 用于开发人员调试的输出,输出可能包含更多详细信息,不保证一致性或美观。 |
{:#?} | Debug | 用于开发人员调试的输出,输出可能包含更多详细信息,保证美观。 |
{:o} | Octal | 用于按八进制形式输出整数。 |
{:x} | LowerHex | 用于按小写十六进制形式输出整数。 |
{:X} | UpperHex | 用于按大写十六进制形式输出整数。 |
{:b} | Binary | 用于按二进制形式输出整数。 |
{:e}, {:E} | LowerExp, UpperExp | 用于按科学计数法输出浮点数,e 用小写字符,E 用大写字符。 |
Display 特性在某种程度上类似于 Java 中每个对象的 toString 方法,它定义了如何将类型以一种用户友好的方式进行格式化输出。另一方面,Debug 特性提供了一个用于调试目的的 “字符串化” 表示,它被很多容器类型所实现,以便在调试时能够方便地输出变量和数据结构的内容。
Rust 特意区分用于普通展示的 toString 功能(Display 特性)和用于调试目的的输出(Debug 特性)。在大多数情况下,如果单独使用 {} 导致编译错误,那么通常可以通过使用 {:?} 来替代,并使其能够成功编译并输出。{:?} 格式说明符表示使用类型所实现的 Debug 特性来进行格式化,从而在标准输出中呈现变量的内部状态,这通常是在开发过程中检查值的快速且不那么正式的方法。
使用 VSCode 调试 Rust 代码
您需要安装扩展程序。选择哪一个取决于你的平台:
接下来需要配置你的 VS Code 启动项。
- 点击
调试->添加配置 - 如果你使用的是 Windows,选择
C++ (Windows) - 如果你使用的是 Mac/Linux,选择
LLDB: Custom Launch - 添加配置应该会创建并打开启动配置文件
launch.json。你必须手动修改配置项"program"对应的可执行文件名称。
{
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceRoot}/target/debug/foo.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceRoot}",
"environment": [],
"externalConsole": true
},
{
"name": "(OSX) Launch",
"type": "lldb",
"request": "launch",
"program": "${workspaceRoot}/target/debug/foo",
"args": [],
"cwd": "${workspaceRoot}",
}
]
}
- 添加一个断点。按 F5 键启动。