Share E-Book

通过例子学rust 中文版 (rust team)(Z-Library)

Author rust team

rust
Language Chinese

No Description

Format PDF
Size 4.8 MB
215
Views
0
Downloads
0.00
Total Donations
(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.

Page 1
通过例子学 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 语言的各种测试手段。 不安全操作 兼容性 补充 - 文档和基准测试
Page 2
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!
Page 3
注释 注释对任何程序都不可缺少,同样 Rust 支持几种不同的注释方式。 普通注释,其内容将被编译器忽略掉: // 单行注释,注释内容直到行尾。 /* 块注释,注释内容一直到结束分隔符。 */ 文档注释,其内容将被解析成 HTML 帮助文档: /// 为接下来的项生成帮助文档。 //! 为注释所属于的项(译注:如 crate、模块或函数)生成帮助文档。 参见: 文档注释 fn main() { // 这是行注释的例子 // 注意有两个斜线在本行的开头 // 在这里面的所有内容都不会被编译器读取 // println!("Hello, world!"); // 请运行一下,你看到结果了吗?现在请将上述语句的两条斜线删掉,并重新运行。 /* * 这是另外一种注释——块注释。一般而言,行注释是推荐的注释格式, * 不过块注释在临时注释大块代码特别有用。/* 块注释可以 /* 嵌套, */ */ * 所以只需很少按键就可注释掉这些 main() 函数中的行。/*/*/* 自己试试!*/*/*/ */ /* 注意,上面的例子中纵向都有 `*`,这只是一种风格,实际上这并不是必须的。 */ // 观察块注释是如何简单地对表达式进行修改的,行注释则不能这样。 // 删除注释分隔符将会改变结果。 let x = 5 + /* 90 + */ 5; println!("Is `x` 10 or 100? x = {}", x); }
Page 4
格式化输出 打印操作由 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)); // 改正 ^ 注释掉此行。 }
Page 5
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
Page 6
调试(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))); }
Page 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); }
Page 8
显示(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) } }
Page 9
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); }
Page 10
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 }
Page 11
测试实例: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); }
Page 12
更改程序使 vector 里面每个元素的下标也能够打印出来。新的结果如下: 参见: for , ref , Result , struct , ? , 和 vec! [0: 1, 1: 2, 2: 3]
Page 13
格式化 我们已经看到,格式化的方式是通过格式字符串来指定的: format!("{}", foo) -> "3735928559" format!("0x{:X}", foo) -> "0xDEADBEEF" format!("0o{:o}", foo) -> "0o33653337357" 根据使用的参数类型是 X、 o 还是未指定,同样的变量( foo)能够格式化 成不同的形式。 这个格式化的功能是通过 trait 实现的,每种参数类型都对应一种 trait。最常见的格式 化 trait 就 是 Display,它可以处理参数类型为未指定的情况,比如 {}。
Page 14
在 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) } }
Page 15
如果感到疑惑,可看下面两条提示: 你可能需要多次列出每个颜色, 你可以使用 :02 补零使位数为 2 位。 参见: std::fmt RGB (128, 255, 90) 0x80FF5A RGB (0, 3, 254) 0x0003FE RGB (0, 0, 0) 0x000000
Page 16
原生类型 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)。
Page 17
参见: 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; }
Page 18
字面量和运算符 整数 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); }
Page 19
元组 元组是一个可以包含各种类型值的组合。元组使用括号 () 来构造(construct),而每个元组自 身又是一个类型标记为 (T1, T2, ...) 的值,其中 T1、 T2 是每个元素的类型。函数可以使用 元组来返回多个值,因为元组可以拥有任意多个值。
Page 20
// 元组可以充当函数的参数和返回值 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) }
The above is a preview of the first 20 pages. Register to read the complete e-book.

Recommended for You

Loading recommended books...
Failed to load, please try again later

Tip the Site

Scan the WeChat Pay or Alipay code to tip. No login required.

WeChat Pay
Alipay
Back to List