Statistics
11
Views
0
Downloads
0
Donations
Support
Share
Uploader

高宏飞

Shared on 2026-03-10

Authorrust team

No description

Tags
No tags
Publish Year: 2022
Language: 中文
Pages: 295
File Format: PDF
File Size: 4.8 MB
Support Statistics
¥.00 · 0times
Text Preview (First 20 pages)
Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

通过例子学 Rust 中文翻译注(Chinese translation of the Rust By Example): 👉 查看更多 Rust 官方文档中英文双语教程,包括双语版《Rust 程序设计语言》(出版书 名为《Rust 权威指南》),本站还提供了 Rust 标准库中文版。 《通过例子学 Rust》(Rust By Example 中文版)翻译自 Rust By Example,中文版最后更新 时间:2022-1-26。查看此书的 Github 翻译项目和源码。 本站支持文档中英文切换,点击页面右上角语言图标可切换到相同章节的英文页面,英文版 每天都会自动同步一次官方的最新版本。 若发现本页表达错误或帮助我们改进翻译,可点击右上角的编辑按钮打开本页对应源码文件 进行编辑和修改,Rust 中文资源的开源组织发展离不开大家,感谢您的支持和帮助! Rust 是一门注重安全(safety)、速度(speed)和并发(concurrency)的现代系统编程语言。 Rust 通过内存安全来实现以上目标,但不使用垃圾回收机制(garbage collection, GC)。 《通过例子学 Rust》(Rust By Example, RBE)内容由一系列可运行的实例组成,通过这些例子 阐明了各种 Rust 的概念和基本库。想获取这些例子外的更多内容,不要忘了安装 Rust 到本地并 查阅官方标准库文档。另外为了满足您的好奇心,您还可以查阅本网站的源代码。 现在让我们开始学习吧! Hello World - 从经典的 “Hello World” 程序开始学习。 原生类型 - 学习有符号整型,无符号整型和其他原生类型。 自定义类型 - 结构体 struct 和 枚举 enum。 变量绑定 - 变量绑定,作用域,变量遮蔽。 类型系统 - 学习改变和定义类型。 类型转换 表达式 流程控制 - if / else, for,以及其他流程控制有关内容。 函数 - 学习方法、闭包和高阶函数。 模块 - 使用模块来组织代码。 Crate - crate 是 Rust 中的编译单元。学习创建一个库。 Cargo - 学习官方的 Rust 包管理工具的一些基本功能。 属性 - 属性是应用于某些模块、crate 或项的元数据(metadata)。 泛型 - 学习编写能够适用于多种类型参数的函数或数据类型。 作用域规则 - 作用域在所有权(ownership)、借用(borrowing)和生命周期(lifetime)中起 着重要作用。 特性 trait - trait 是对未知类型( Self)定义的方法集。 宏 错误处理 - 学习 Rust 语言处理失败的方式。 标准库类型 - 学习 std 标准库提供的一些自定义类型。 标准库更多介绍 - 更多关于文件处理、线程的自定义类型。 测试 - Rust 语言的各种测试手段。 不安全操作 兼容性 补充 - 文档和基准测试
Hello World 这是传统的 Hello World 程序的源码。 println! 是一个宏(macros),可以将文本输出到控制台(console)。 使用 Rust 的编译器 rustc 可以从源程序生成可执行文件: 使用 rustc 编译后将得到可执行文件 hello。 动手试一试 单击上面的 "Run" 按钮并观察输出结果。然后增加一行代码,再一次使用宏 println!,得到下 面结果: // 这是注释内容,将会被编译器忽略掉 // 可以单击那边的按钮 "Run" 来测试这段代码 -> // 若想用键盘操作,可以使用快捷键 "Ctrl + Enter" 来运行 // 这段代码支持编辑,你可以自由地修改代码! // 通过单击 "Reset" 按钮可以使代码恢复到初始状态 -> // 这是主函数 fn main() { // 调用编译生成的可执行文件时,这里的语句将被运行。 // 将文本打印到控制台 println!("Hello World!"); } $ rustc hello.rs $ ./hello Hello World! Hello World! I'm a Rustacean!
注释 注释对任何程序都不可缺少,同样 Rust 支持几种不同的注释方式。 普通注释,其内容将被编译器忽略掉: // 单行注释,注释内容直到行尾。 /* 块注释,注释内容一直到结束分隔符。 */ 文档注释,其内容将被解析成 HTML 帮助文档: /// 为接下来的项生成帮助文档。 //! 为注释所属于的项(译注:如 crate、模块或函数)生成帮助文档。 参见: 文档注释 fn main() { // 这是行注释的例子 // 注意有两个斜线在本行的开头 // 在这里面的所有内容都不会被编译器读取 // println!("Hello, world!"); // 请运行一下,你看到结果了吗?现在请将上述语句的两条斜线删掉,并重新运行。 /* * 这是另外一种注释——块注释。一般而言,行注释是推荐的注释格式, * 不过块注释在临时注释大块代码特别有用。/* 块注释可以 /* 嵌套, */ */ * 所以只需很少按键就可注释掉这些 main() 函数中的行。/*/*/* 自己试试!*/*/*/ */ /* 注意,上面的例子中纵向都有 `*`,这只是一种风格,实际上这并不是必须的。 */ // 观察块注释是如何简单地对表达式进行修改的,行注释则不能这样。 // 删除注释分隔符将会改变结果。 let x = 5 + /* 90 + */ 5; println!("Is `x` 10 or 100? x = {}", x); }
格式化输出 打印操作由 std::fmt 里面所定义的一系列宏来处理,包括: format!:将格式化文本写到字符串。 print!:与 format! 类似,但将文本输出到控制台(io::stdout)。 println! : 与 print! 类似,但输出结果追加一个换行符。 eprint!:与 print! 类似,但将文本输出到标准错误(io::stderr)。 eprintln!:与 eprint! 类似,但输出结果追加一个换行符。 这些宏都以相同的做法解析文本。有个额外优点是格式化的正确性会在编译时检查。 fn main() { // 通常情况下,`{}` 会被任意变量内容所替换。 // 变量内容会转化成字符串。 println!("{} days", 31); // 不加后缀的话,31 就自动成为 i32 类型。 // 你可以添加后缀来改变 31 的类型(例如使用 31i64 声明 31 为 i64 类型)。 // 用变量替换字符串有多种写法。 // 比如可以使用位置参数。 println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // 可以使用命名参数。 println!("{subject} {verb} {object}", object="the lazy dog", subject="the quick brown fox", verb="jumps over"); // 可以在 `:` 后面指定特殊的格式。 println!("{} of {:b} people know binary, the other half don't", 1, 2); // 你可以按指定宽度来右对齐文本。 // 下面语句输出 " 1",5 个空格后面连着 1。 println!("{number:>width$}", number=1, width=6); // 你可以在数字左边补 0。下面语句输出 "000001"。 println!("{number:>0width$}", number=1, width=6); // println! 会检查使用到的参数数量是否正确。 println!("My name is {0}, {1} {0}", "Bond"); // 改正 ^ 补上漏掉的参数:"James" // 创建一个包含单个 `i32` 的结构体(structure)。命名为 `Structure`。 #[allow(dead_code)] struct Structure(i32); // 但是像结构体这样的自定义类型需要更复杂的方式来处理。 // 下面语句无法运行。 println!("This struct `{}` won't print...", Structure(3)); // 改正 ^ 注释掉此行。 }
std::fmt 包含多种 trait(特质)来控制文字显示,其中重要的两种 trait 的基本形式如下: fmt::Debug:使用 {:?} 标记。格式化文本以供调试使用。 fmt::Display:使用 {} 标记。以更优雅和友好的风格来格式化文本。 上例使用了 fmt::Display,因为标准库提供了那些类型的实现。若要打印自定义类型的文本, 需要更多的步骤。 动手试一试 改正上面代码中的两个错误(见 “改正”),使它可以没有错误地运行。 再用一个 println! 宏,通过控制显示的小数位数来打印: Pi is roughly 3.142(Pi 约等于 3.142)。为了达到练习目的,使用 let pi = 3.141592 作为 Pi 的近似值(提示:设置小数位的 显示格式可以参考文档 std::fmt)。 参见: std::fmt , macros , struct 和 trait
调试(Debug) 所有的类型,若想用 std::fmt 的格式化打印,都要求实现至少一个可打印的 traits。 自动的 实现只为一些类型提供,比如 std 库中的类型。所有其他类型 都必须手动实现。 fmt::Debug 这个 trait 使这项工作变得相当简单。所有类型都能推导( derive,即自 动创 建) fmt::Debug 的实现。但是 fmt::Display 需要手动实现。 所有 std 库类型都天生可以使用 {:?} 来打印: 所以 fmt::Debug 确实使这些内容可以打印,但是牺牲了一些美感。Rust 也通过 {:#?} 提供了 “美化打印” 的功能: // 这个结构体不能使用 `fmt::Display` 或 `fmt::Debug` 来进行打印。 struct UnPrintable(i32); // `derive` 属性会自动创建所需的实现,使这个 `struct` 能使用 `fmt::Debug` 打印。 #[derive(Debug)] struct DebugPrintable(i32); // 推导 `Structure` 的 `fmt::Debug` 实现。 // `Structure` 是一个包含单个 `i32` 的结构体。 #[derive(Debug)] struct Structure(i32); // 将 `Structure` 放到结构体 `Deep` 中。然后使 `Deep` 也能够打印。 #[derive(Debug)] struct Deep(Structure); fn main() { // 使用 `{:?}` 打印和使用 `{}` 类似。 println!("{:?} months in a year.", 12); println!("{1:?} {0:?} is the {actor:?} name.", "Slater", "Christian", actor="actor's"); // `Structure` 也可以打印! println!("Now {:?} will print!", Structure(3)); // 使用 `derive` 的一个问题是不能控制输出的形式。 // 假如我只想展示一个 `7` 怎么办? println!("Now {:?} will print!", Deep(Structure(7))); }
你可以通过手动实现 fmt::Display 来控制显示效果。 参见: attributes, derive , std::fmt 和 struct #[derive(Debug)] struct Person<'a> { name: &'a str, age: u8 } fn main() { let name = "Peter"; let age = 27; let peter = Person { name, age }; // 美化打印 println!("{:#?}", peter); }
显示(Display) fmt::Debug 通常看起来不太简洁,因此自定义输出的外观经常是更可取的。这需要通过 手动实 现 fmt::Display 来做到。 fmt::Display 采用 {} 标记。实现方式看 起来像这样: fmt::Display 的效果可能比 fmt::Debug 简洁,但对于 std 库来说,这就有一个问 题。模棱 两可的类型该如何显示呢?举个例子,假设标准库对所有的 Vec<T> 都实现了同 一种输出样式, 那么它应该是哪种样式?下面两种中的一种吗? Vec<path>: /:/etc:/home/username:/bin(使用 : 分割) Vec<number>: 1,2,3(使用 , 分割) 我们没有这样做,因为没有一种合适的样式适用于所有类型,标准库也并不擅自规定一种样 式。 对于 Vec<T> 或其他任意泛型容器(generic container), fmt::Display 都没有 实现。因此在 这些泛型的情况下要用 fmt::Debug。 这并不是一个问题,因为对于任何非泛型的容器类型, fmt::Display 都能够实 现。 // (使用 `use`)导入 `fmt` 模块使 `fmt::Display` 可用 use std::fmt; // 定义一个结构体,咱们会为它实现 `fmt::Display`。以下是个简单的元组结构体 // `Structure`,包含一个 `i32` 元素。 struct Structure(i32); // 为了使用 `{}` 标记,必须手动为类型实现 `fmt::Display` trait。 impl fmt::Display for Structure { // 这个 trait 要求 `fmt` 使用与下面的函数完全一致的函数签名 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 仅将 self 的第一个元素写入到给定的输出流 `f`。返回 `fmt:Result`,此 // 结果表明操作成功或失败。注意 `write!` 的用法和 `println!` 很相似。 write!(f, "{}", self.0) } }
use std::fmt; // 导入 `fmt` // 带有两个数字的结构体。推导出 `Debug`,以便与 `Display` 的输出进行比较。 #[derive(Debug)] struct MinMax(i64, i64); // 实现 `MinMax` 的 `Display`。 impl fmt::Display for MinMax { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 使用 `self.number` 来表示各个数据。 write!(f, "({}, {})", self.0, self.1) } } // 为了比较,定义一个含有具名字段的结构体。 #[derive(Debug)] struct Point2D { x: f64, y: f64, } // 类似地对 `Point2D` 实现 `Display` impl fmt::Display for Point2D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 自定义格式,使得仅显示 `x` 和 `y` 的值。 write!(f, "x: {}, y: {}", self.x, self.y) } } fn main() { let minmax = MinMax(0, 14); println!("Compare structures:"); println!("Display: {}", minmax); println!("Debug: {:?}", minmax); let big_range = MinMax(-300, 300); let small_range = MinMax(-3, 3); println!("The big range is {big} and the small is {small}", small = small_range, big = big_range); let point = Point2D { x: 3.3, y: 7.2 }; println!("Compare points:"); println!("Display: {}", point); println!("Debug: {:?}", point); // 报错。`Debug` 和 `Display` 都被实现了,但 `{:b}` 需要 `fmt::Binary` // 得到实现。这语句不能运行。 // println!("What does Point2D look like in binary: {:b}?", point); }
fmt::Display 被实现了,而 fmt::Binary 没有,因此 fmt::Binary 不能使用。 std::fmt 有很多这样的 trait,它们都要求有各自的实现。这些内容将在 后面的 std::fmt 章节中详细介 绍。 动手试一试 检验上面例子的输出,然后在示例程序中,仿照 Point2D 结构体增加一个复数结构体。 使用一 样的方式打印,输出结果要求是这个样子: 参见: derive , std::fmt , macros, struct , trait , 和 use Display: 3.3 + 7.2i Debug: Complex { real: 3.3, imag: 7.2 }
测试实例:List 对一个结构体实现 fmt::Display,其中的元素需要一个接一个地处理到,这可能会很麻 烦。问 题在于每个 write! 都要生成一个 fmt::Result。正确的实现需要 处理所有的 Result。Rust 专 门为解决这个问题提供了 ? 操作符。 在 write! 上使用 ? 会像是这样: 另外,你也可以使用 try! 宏,它和 ? 是一样的。这种写法比较罗嗦,故不再推荐, 但在老一 些的 Rust 代码中仍会看到。使用 try! 看起来像这样: 有了 ?,对一个 Vec 实现 fmt::Display 就很简单了: 动手试一试: // 对 `write!` 进行尝试(try),观察是否出错。若发生错误,返回相应的错误。 // 否则(没有出错)继续执行后面的语句。 write!(f, "{}", value)?; try!(write!(f, "{}", value)); use std::fmt; // 导入 `fmt` 模块。 // 定义一个包含单个 `Vec` 的结构体 `List`。 struct List(Vec<i32>); impl fmt::Display for List { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 使用元组的下标获取值,并创建一个 `vec` 的引用。 let vec = &self.0; write!(f, "[")?; // 使用 `v` 对 `vec` 进行迭代,并用 `count` 记录迭代次数。 for (count, v) in vec.iter().enumerate() { // 对每个元素(第一个元素除外)加上逗号。 // 使用 `?` 或 `try!` 来返回错误。 if count != 0 { write!(f, ", ")?; } write!(f, "{}", v)?; } // 加上配对中括号,并返回一个 fmt::Result 值。 write!(f, "]") } } fn main() { let v = List(vec![1, 2, 3]); println!("{}", v); }
更改程序使 vector 里面每个元素的下标也能够打印出来。新的结果如下: 参见: for , ref , Result , struct , ? , 和 vec! [0: 1, 1: 2, 2: 3]
格式化 我们已经看到,格式化的方式是通过格式字符串来指定的: format!("{}", foo) -> "3735928559" format!("0x{:X}", foo) -> "0xDEADBEEF" format!("0o{:o}", foo) -> "0o33653337357" 根据使用的参数类型是 X、 o 还是未指定,同样的变量( foo)能够格式化 成不同的形式。 这个格式化的功能是通过 trait 实现的,每种参数类型都对应一种 trait。最常见的格式 化 trait 就 是 Display,它可以处理参数类型为未指定的情况,比如 {}。
在 fmt::fmt 文档中可以查看格式化 traits 一览表和它们的参 数类型。 动手试一试 为上面的 Color 结构体实现 fmt::Display,应得到如下的输出结果: use std::fmt::{self, Formatter, Display}; struct City { name: &'static str, // 纬度 lat: f32, // 经度 lon: f32, } impl Display for City { // `f` 是一个缓冲区(buffer),此方法必须将格式化后的字符串写入其中 fn fmt(&self, f: &mut Formatter) -> fmt::Result { let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' }; let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' }; // `write!` 和 `format!` 类似,但它会将格式化后的字符串写入 // 一个缓冲区(即第一个参数f)中。 write!(f, "{}: {:.3}°{} {:.3}°{}", self.name, self.lat.abs(), lat_c, self.lon.abs(), lon_c) } } #[derive(Debug)] struct Color { red: u8, green: u8, blue: u8, } fn main() { for city in [ City { name: "Dublin", lat: 53.347778, lon: -6.259722 }, City { name: "Oslo", lat: 59.95, lon: 10.75 }, City { name: "Vancouver", lat: 49.25, lon: -123.1 }, ].iter() { println!("{}", *city); } for color in [ Color { red: 128, green: 255, blue: 90 }, Color { red: 0, green: 3, blue: 254 }, Color { red: 0, green: 0, blue: 0 }, ].iter() { // 在添加了针对 fmt::Display 的实现后,请改用 {} 检验效果。 println!("{:?}", *color) } }
如果感到疑惑,可看下面两条提示: 你可能需要多次列出每个颜色, 你可以使用 :02 补零使位数为 2 位。 参见: std::fmt RGB (128, 255, 90) 0x80FF5A RGB (0, 3, 254) 0x0003FE RGB (0, 0, 0) 0x000000
原生类型 Rust 提供了多种原生类型( primitives),包括: 标量类型(scalar type) 有符号整数(signed integers): i8、 i16、 i32、 i64、 i128 和 isize(指针宽度) 无符号整数(unsigned integers): u8、 u16、 u32、 u64、 u128 和 usize(指针宽度) 浮点数(floating point): f32、 f64 char(字符):单个 Unicode 字符,如 'a', 'α' 和 '∞'(每个都是 4 字节) bool(布尔型):只能是 true 或 false 单元类型(unit type): ()。其唯一可能的值就是 () 这个空元组 尽管单元类型的值是个元组,它却并不被认为是复合类型,因为并不包含多个值。 复合类型(compound type) 数组(array):如 [1, 2, 3] 元组(tuple):如 (1, true) 变量都能够显式地给出类型说明(type annotation)。数字还可以通过后缀(suffix)或默认方 式来声明类型。整型默认为 i32 类型,浮点型默认为 f64类型。注意 Rust 还可以根据上下文来 推断(infer)类型(译注:比如一个未声明类型整数和 i64 的整数相加,则该整数会自动推断为 i64 类型。仅当根据环境无法推断时,才按默认方式取整型数值为 i32,浮点数值为 f64)。
参见: std 库、 mut、类型推断 和 变量遮蔽 fn main() { // 变量可以给出类型说明。 let logical: bool = true; let a_float: f64 = 1.0; // 常规说明 let an_integer = 5i32; // 后缀说明 // 否则会按默认方式决定类型。 let default_float = 3.0; // `f64` let default_integer = 7; // `i32` // 类型也可根据上下文自动推断。 let mut inferred_type = 12; // 根据下一行的赋值推断为 i64 类型 inferred_type = 4294967296i64; // 可变的(mutable)变量,其值可以改变。 let mut mutable = 12; // Mutable `i32` mutable = 21; // 报错!变量的类型并不能改变。 mutable = true; // 但可以用遮蔽(shadow)来覆盖前面的变量。 let mutable = true; }
字面量和运算符 整数 1、浮点数 1.2、字符 'a'、字符串 "abc"、布尔值 true 和单元类型 () 可以用数字、 文字或符号之类的 “字面量”(literal)来表示。 另外,通过加前缀 0x、 0o、 0b,数字可以用十六进制、八进制或二进制记法表示。 为了改善可读性,可以在数值字面量中插入下划线,比如: 1_000 等同于 1000, 0.000_001 等 同于 0.000001。 我们需要把字面量的类型告诉编译器。如前面学过的,我们使用 u32 后缀来表明字面量是一个 32 位无符号整数, i32 后缀表明字面量是一个 32 位有符号整数。 Rust 提供了一系列的运算符(operator),它们的优先级和类 C 语言类似。(译注:类 C 语言包 括 C/C++、Java、PHP 等语言) fn main() { // 整数相加 println!("1 + 2 = {}", 1u32 + 2); // 整数相减 println!("1 - 2 = {}", 1i32 - 2); // 试一试 ^ 尝试将 `1i32` 改为 `1u32`,体会为什么类型声明这么重要 // 短路求值的布尔逻辑 println!("true AND false is {}", true && false); println!("true OR false is {}", true || false); println!("NOT true is {}", !true); // 位运算 println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101); println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101); println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101); println!("1 << 5 is {}", 1u32 << 5); println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2); // 使用下划线改善数字的可读性! println!("One million is written as {}", 1_000_000u32); }
元组 元组是一个可以包含各种类型值的组合。元组使用括号 () 来构造(construct),而每个元组自 身又是一个类型标记为 (T1, T2, ...) 的值,其中 T1、 T2 是每个元素的类型。函数可以使用 元组来返回多个值,因为元组可以拥有任意多个值。
// 元组可以充当函数的参数和返回值 fn reverse(pair: (i32, bool)) -> (bool, i32) { // 可以使用 `let` 把一个元组的成员绑定到一些变量 let (integer, boolean) = pair; (boolean, integer) } // 在 “动手试一试” 的练习中要用到下面这个结构体。 #[derive(Debug)] struct Matrix(f32, f32, f32, f32); fn main() { // 包含各种不同类型的元组 let long_tuple = (1u8, 2u16, 3u32, 4u64, -1i8, -2i16, -3i32, -4i64, 0.1f32, 0.2f64, 'a', true); // 通过元组的下标来访问具体的值 println!("long tuple first value: {}", long_tuple.0); println!("long tuple second value: {}", long_tuple.1); // 元组也可以充当元组的元素 let tuple_of_tuples = ((1u8, 2u16, 2u32), (4u64, -1i8), -2i16); // 元组可以打印 println!("tuple of tuples: {:?}", tuple_of_tuples); // 但很长的元组无法打印 // let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); // println!("too long tuple: {:?}", too_long_tuple); // 试一试 ^ 取消上面两行的注释,阅读编译器给出的错误信息。 let pair = (1, true); println!("pair is {:?}", pair); println!("the reversed pair is {:?}", reverse(pair)); // 创建单元素元组需要一个额外的逗号,这是为了和被括号包含的字面量作区分。 println!("one element tuple: {:?}", (5u32,)); println!("just an integer: {:?}", (5u32)); // 元组可以被解构(deconstruct),从而将值绑定给变量 let tuple = (1, "hello", 4.5, true); let (a, b, c, d) = tuple; println!("{:?}, {:?}, {:?}, {:?}", a, b, c, d); let matrix = Matrix(1.1, 1.2, 2.1, 2.2); println!("{:?}", matrix) }