Hello, Rust!
Hello, Rust!
久违的学习笔记系列!!
天天复习考研,吐了。划一天水放松一下吧,学学 Rust。
本文是学习以下(主要)内容的笔记:
Rust 官网 get started: https://www.rust-lang.org/zh-CN/learn/get-started
Rust By Example (中文译本) 1. Hello World: http://rustwiki.org/zh-CN/rust-by-example/hello.html
- Rust 文档
std::fmt
: https://doc.rust-lang.org/std/fmt/
安装
以 *nix
系统为例,用 rustup
:
1 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
安装检查:
1 | cargo --version |
(cargo
是构建、包管理的工具,Rust 的编译器是 rustc
)
更新:
1 | rustup update |
Hello World
可以只写一个文件,也可以做成一个项目。
单一源文件
新建一个 hello.rs
:
1 | fn main() { |
编译、运行:
1 | $ rustc hello.rs |
在我的电脑上(Intel macBook Pro 2017,macOS 11.4)编译出来的二进制文件(./hello
)有 408K
。同样功能的 C 程序只有 48K
,Swift 为 49K
,而 Go 程序要 1.2M
。
Cargo 项目
新建项目:
1 | cargo new hello-rust |
生成一个项目目录 ./hello-rust/
,其内容如下:
1 | hello-rust |
(它甚至给 git init
了,还写了 .gitignore
)
main.rs
里面已经写好了一个 hello world:
1 | fn main() { |
运行:
1 | cargo run |
注释
1 | // 单行注释 |
文档注释:
1 | /// 文档注释 |
用 rustdoc hello.rs
来编译文档,结果是个网站,放在 ./doc
里。随便开个服务就可以看了:
1 | python3 -m http.server -d doc |
格式化输出
由 std 里的一堆宏实现格式化输出(这些 !
就说明是宏):
- 格式化字符串:
format!
- 标准输出:
print!
,println!
- 标准错误:
eprint!
,eprintln!
这些东西的语法和 C 的 printf
类似,都是 format!("a string literal", ...)
,具体的用法见 https://doc.rust-lang.org/std/fmt/index.html。下面只简要介绍:
format!
是返回格式化后的字符串, 而四种 print
就是把 format!
的结果写到 io::stdout
或 io::stderr
。
在格式字符串字面值常量中用 {name_or_index:formatting}
来指定替换内容。
替换也就要把一个特定类型的对象格式化(toString
)。格式化一个对象的方式在 Rust 中叫做 traits。Rust 有好几个不同的 traits,所以同一个数据可以有好几种显示的方式,例如一个数字可以写成二进制、十进制、十六进制等等。每个 traits 对应有一种 {:口}
(口
是某种字符)。
最常用的两种 traits 是 Display 和 Debug,二者在格式字符串中分别写作 {}
(也就是{:口}
的 口
为空的情形) 和 {:?}
。二者分别和 Go 语言 fmt 包的 %v
和 %#v
类似。
1 | // `{}` 是 `fmt::Display`,用来显示任意内容,优雅格式 |
{:}
的冒号前面可以给替换字符串命名,或者指定位置:
1 | println!("Hello, {name} with number {a}!", name="foo", a="666"); |
{:}
的冒号后面是格式:
1 | println!("dec: {0}, bin: {0:b}, hex: {0:X}", 66); |
具体格式的写法参考 https://doc.rust-lang.org/std/fmt/ 。大概的写法为 FA+#?0W.PT
:
F
是填充用的字符A
是<
或^
或>
,对齐的方向(不满用 F 填充)+
:正数显示加号#?
:pretty-print 的 Debug#b
,#o
,#x
:显示二、八、十六进制的前缀
0
数字前面补零至宽度W
宽度.P
小数位数.N$
:用参数 N 做精度(就是format!("{}", ...)
的...
,从 0 开始)- 如果是整数,
{:N$}
,就是那参数 N 作宽度。
- 如果是整数,
.*
:读两个二个参数,后一个参数是要格式化的小数,以前一个参数为精度,format!("{:.*}", 5, 0.01)
结果为0.01000
T
是显示的方式,即 traits(可以让自定义类型自己定制各种显示效果):
1 | println!("{:_<9} {:09} {:`^9} {:9.6} {:09.6} {:)>9} {:#?}", "left", 123, "center", 3.14, 3.14, "right", 2.17e5); |
所有的类型,若想用 std::fmt
的格式化打印,都要求实现至少一个可打印的 traits
。 自动的实现只为一些类型提供,比如 std
库中的类型。所有其他类型 都必须手动实现。下面介绍如何输出自定义类型:
Debug
fmt::Debug
,就是 {:?}
, 比较容易实现,一般直接用推导(derive
)来自动创建:
1 | // 一个自定义的结构体 |
Display
fmt::Display
,就是 {}
, 须手动实现。
1 | use std::fmt; |
注意上面这个例子中 write!(...)
后面不加 ;
。这个语法是 return write!(...);
的简写,以最后的表达式作为函数返回值。如果不写 return
又加了 ;
就变成返回空 ()
了。
用类似的方法,还可以实现其他的 traits,比如:Octal
,LowerHex
, UpperHex
, Pointer
,Binary
,LowerExp
, UpperExp
。
错误处理
对于一个集合,要 fmt 输出的话,需要迭代写每个值:
1 | use std::fmt; // 导入 `fmt` 模块。 |
这里就需要多次调用 write!
,write!
是可能返回错误的哦。一旦错误了就不该继续写了,所以在调用末尾用了 ?
语法:
1 | write!(f, "{}", value)?; |
这个语法是尝试 write!
,若发生错误,则直接返回相应的错误,结束函数;否则继续执行后面的语句。也就是类似于 Go 中的:
1 | _, err := DoSomething() |
如果不加这个 ?
默认会编译时 warning,提醒你可能没处理错误。
格式化
再看一个例子:
1 | use std::fmt::{self, Formatter, Display}; |
输出:
1 | RGB(128, 255, 90) 0x80FF5A |