rust 快速入门——4 Hello world

130 阅读4分钟

Hello World

普若哥们儿

github.com/wu-hongbing…

gitee.com/wuhongbing/…

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.crateferris-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 键启动。