Document not found (404)
-This URL is invalid, sorry. Please use the navigation bar or search to continue.
- -diff --git a/.gitignore b/.gitignore index 8b13789..e9c0728 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ - +book \ No newline at end of file diff --git a/book/.nojekyll b/book/.nojekyll deleted file mode 100644 index f173110..0000000 --- a/book/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/book/404.html b/book/404.html deleted file mode 100644 index c658dbc..0000000 --- a/book/404.html +++ /dev/null @@ -1,218 +0,0 @@ - - -
- - -This URL is invalid, sorry. Please use the navigation bar or search to continue.
- -"The code is more what you'd call 'guidelines' than actual rules." – Hector Barbossa
-
-斯科特·迈耶斯的《Effective C++》因为引入了一种新式的编程书籍风格而取得非凡的成功,这种风格专注于一系列从C++软件开发实际经验中学到的指导原则的集合。值得注意的是,这些指导原则不仅解释它们存在的必要性而且还允许读者自己决定他们特定的场景是否需要打破这些指导规则。
-《Effective C++》的第一版出版于1992年,那时的C++语言虽然年轻,但已经是一种包含许多潜在问题的微妙语言;拥有一个指导其不同特性相互作用的指南是必不可少的。
-相比之下,Rust也是一门年轻的语言,但它几乎不含潜在的问题。其类型系统的强度一致性意味着,如果一个Rust程序编译通过了,它就已经有相当大的可能性会正常工作——这种现象以前只在更学术性、不那么容易感知到的语言中观察到,比如Haskell。
-Rust安全性——包括类型安全和内存安全 —— 虽然有代价。Rust因其学习曲线陡峭而闻名,入门者必须经历与借用检查器斗争、重新设计数据结构以及对生命周期感到困惑的入门仪式。一个编译通过的Rust程序可能很有可能直接工作,但让它编译通过内心所遭受的斗争是真实的——即使Rust编译器提供的错误诊断非常有帮助。
-因此,这本书的目标读者与其他《Effective
Rust的安全性也导致了完全没有标题为“永不……”的项目。如果你真的不应该做某事,编译器通常会阻止你这么做。(译者备注:这就是Rust编译器的自信,不该干的,不会让你干)
-尽管如此,文本仍然假定读者对语言的基础有所了解。它还假设使用的是2018版的Rust,并使用稳定的工具链。
-用于代码片段和错误消息的具体rustc版本是1.60。Rust现在已经足够稳定(并有足够的向后兼容保证),代码片段不太可能需要为后续版本做出更改,但错误消息可能会因你的特定编译器版本而有所不同。
-文本还有许多参考和与C++的比较,因为这可能是最接近的等效语言(特别是具有C++11的移动语义),也是Rust新手最可能遇到的前一种语言。
-所有的项目被分为如下六个片段:
-虽然“概念”部分可以说比“类型”部分更为基础,但它故意放在第二位,以便从头到尾阅读的读者可以先建立一些信心。(其实我们懂作者这句话,从我的理解,应该是第二部分也给一点简单的东西,好让有继续读下去的信心)
- -这本书的第一部分涵盖了围绕Rust类型系统的建议。这个类型系统比其他主流语言的类型系统更具表达力;它与诸如OCaml或Haskell这样的“学术”语言有更多共同之处。
-其中一个核心部分是Rust的枚举类型,这比其他语言中的枚举类型具有更高的表达力,并且允许使用代数数据类型。
-Rust类型系统的另一个核心支柱是特征类型(trait type)。特征大致相当于其他语言中的接口类型,但它们也与Rust的泛型(项目12)相关联,以允许在没有运行时开销的情况下重用接口。
- -"who called them programers and not type writers" – @thingskatedid
-
-Rust类型系统的基础对于来自另一种静态类型编程语言(如C++、Go或Java)的人来说相当熟悉。这里有一系列具体大小的整数类型,包括有符号(i8
、i16
、i32
、i64
、i128
)和无符号(u8
、u16
、u32
、u64
、u128
)。
还有有符号(isize
)和无符号(usize
)整数,其大小与目标系统上的指针大小相匹配。Rust不是一种需要在指针和整数之间进行大量转换的语言,因此这种描述并不真正相关。然而,标准集合以usize
(来自.len()
)返回它们的大小,所以集合索引意味着usize值相当常见——从容量角度看这显然是可以的,因为系统上的内存地址数量多于内存中集合的项目数量。
整数类型确实给我们提供了第一个暗示,即Rust是一个比C++更严格的世界——试图将(i32
)放入(i16
)的容器中会生成一个编译时错误。
-#![allow(unused)] -fn main() { - let x: i32 = 42; - let y: i16 = x; -}
这段代码会产生如下错误:
--#![allow(unused)] -fn main() { -error[E0308]: mismatched types - --> use-types/src/main.rs:14:22 - | -14 | let y: i16 = x; - | --- ^ expected `i16`, found `i32` - | | - | expected due to this - | -help: you can convert an `i32` to an `i16` and panic if the converted value doesn't fit - | -14 | let y: i16 = x.try_into().unwrap(); - | ++++++++++++++++++++ -}
这令人非常安心:Rust不会在程序员进行风险操作时静静地坐在那里。Rust不但显示出有更严格的规则,而且它也有有用的编译器消息,指出如何遵守这些规则。建议的解决方案引发了一个问题,即如何处理转换可能改变值的情况,我们稍后还会在错误处理(项目4)和使用panic!(项目18)上有更多讨论。
-Rust还不允许一些可能看似“安全”的操作:
--#![allow(unused)] -fn main() { - let x = 42i32; // Integer literal with type suffix - let y: i64 = x; -}
这段代码会产生如下错误:
--#![allow(unused)] -fn main() { -error[E0308]: mismatched types - --> use-types/src/main.rs:23:22 - | -23 | let y: i64 = x; - | --- ^ expected `i64`, found `i32` - | | - | expected due to this - | -help: you can convert an `i32` to an `i64` - | -23 | let y: i64 = x.into(); - | +++++++ - -}
在这里,建议的解决方案并没有提出错误处理的问题,但转换仍然需要是显式的。我们稍后将更详细地讨论类型转换(项目6)。
-继续介绍基本的原始类型,Rust包括用于布尔值的bool
类型,浮点类型(f32
、f64
)以及类似于C语言中的void的单位类型()
。
更值得注意的是,Rust具有char
字符类型,它存储一个Unicode值,类似于Go的rune
类型。虽然内部存储为4字节,但Rust不允许将其自由地转换为32位整数或从32位整数转换得来
这种类型系统的严谨性强制你在代码中明确表达 —— 一个u32
值与一个char
不同,与一串UTF-8字节不同,与一串任意字节不同。定义你正在操作的内存取决于你。乔尔·斯波尔斯基(Joel Spolsky)关于Unicode的著名博客文章可以帮助理解这些差异。
虽然Rust提供了帮助方法来转换这些不同的类型,但它们的签名要求你管理或明确忽略转换错误的风险。例如,任何Unicode代码点都可以用32位表示,因此将'a'转换为u32是允许的。然而,从u32转换为char则更复杂,因为可能存在无效的Unicode代码点。
-char::from_u32
返回一个Option<char>
,强制要求调用者来处理错误的情况char::from_u32_unchecked
假定输入是有效的Unicode代码点,这样就可以避免运行时检查,但是它的结果是需要标记位unsafe
的,强制调用者也是unsafe的(项目16相关)继续讨论聚合类型,Rust有:
-元组结构是结构体和元组的混合体:整体类型有一个名称,但个别字段没有名称——它们通过数字来引用,例如:s.0, s.1 等。
--#![allow(unused)] -fn main() { - struct TextMatch(usize, String); - let m = TextMatch(12, "needle".to_owned()); - assert_eq!(m.0, 12); -}
这引出了Rust类型系统中的王冠上的宝石——enum。
-在其基本形式中,很难看出有什么值得激动的地方。与其他语言类似,枚举允许你指定一组互斥的值,可能附带有数字或字符串值。
--#![allow(unused)] -fn main() { - enum HttpResultCode { - Ok = 200, - NotFound = 404, - Teapot = 418, - } - let code = HttpResultCode::NotFound; - assert_eq!(code as i32, 404); -}
因为每一个enum
定义创建了不同的类型,这个可以提高如下函数的可读性以及维护性:
-#![allow(unused)] -fn main() { - print_page(/* both_sides= */ true, /* colour= */ false); -}
上述函数的使用enum
的新版本:
-#![allow(unused)] -fn main() { - pub enum Sides { - Both, - Single, - } - - pub enum Output { - BlackAndWhite, - Colour, - } - - pub fn print_page(sides: Sides, colour: Output) { - // ... - } -}
这种方式类型安全更加有保障,而且给调用者来说也是更加直观的。
--#![allow(unused)] -fn main() { - print_page(Sides::Both, Output::BlackAndWhite); -}
不像bool版本,如果库的使用者不小心将参数的顺序搞错了,那么编译器就会立即抱怨,让你知道你犯了一个错误。
--#![allow(unused)] -fn main() { -error[E0308]: mismatched types - --> use-types/src/main.rs:89:20 - | -89 | print_page(Output::BlackAndWhite, Sides::Single); - | ^^^^^^^^^^^^^^^^^^^^^ expected enum `enums::Sides`, found enum `enums::Output` -error[E0308]: mismatched types - --> use-types/src/main.rs:89:43 - | -89 | print_page(Output::BlackAndWhite, Sides::Single); - | ^^^^^^^^^^^^^ expected enum `enums::Output`, found enum `enums::Sides` - -}
(使用新类型模式(项目7)来包装布尔值也可以实现类型安全和可维护性;如果语义将始终是布尔值,通常最好使用该方法,并且如果将来可能出现新的替代项(例如 Sides::BothAlternateOrientation),则使用枚举。)
-Rust的枚举的类型安全性在匹配表达式中继续体现:
--#![allow(unused)] -fn main() { - let msg = match code { - HttpResultCode::Ok => "Ok", - HttpResultCode::NotFound => "Not found", - // forgot to deal with the all-important "I'm a teapot" code - }; -}
这段代码会产生如下错误:
--#![allow(unused)] -fn main() { -error[E0004]: non-exhaustive patterns: `Teapot` not covered - --> use-types/src/main.rs:65:25 - | -51 | / enum HttpResultCode { -52 | | Ok = 200, -53 | | NotFound = 404, -54 | | Teapot = 418, - | | ------ not covered -55 | | } - | |_____- `HttpResultCode` defined here -... -65 | let msg = match code { - | ^^^^ pattern `Teapot` not covered - | - = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms - = note: the matched value is of type `HttpResultCode` - -}
编译器强制程序员考虑枚举表示的所有可能性,即使结果只是添加一个默认分支 _ => {}。(需要注意的是,现代C++编译器也可以警告缺少枚举switch
分支。)
Rust枚举功能的真正威力来自于每个变体都可以携带数据,使其成为代数数据类型(ADT)。对于主流语言的程序员来说,这可能不太熟悉;从C/C++的角度来看,它就像是枚举与联合的组合——只不过是类型安全的。
-这意味着程序数据结构的不变性可以编码到Rust的类型系统中;不符合这些不变性的状态甚至不会编译通过。一个设计良好的枚举能够使创作者的意图清晰地传达给人类和编译器:
--#![allow(unused)] -fn main() { -pub enum SchedulerState { - Inert, - Pending(HashSet<Job>), - Running(HashMap<CpuId, Vec<Job>>), -} -}
仅仅从类型定义,我们可以合理地猜测,任务会在挂起状态中排队,直到调度程序完全激活,然后它们会分配给每个CPU池。
-这突显了这个项目的中心主题,即利用Rust的类型系统来表达与软件设计相关的概念。
-一个明显的迹象表明这种情况并未发生是,当一些字段或参数的有效性需要解释时,会有注释说明:
--#![allow(unused)] -fn main() { -struct DisplayProps { - x: u32, - y: u32, - monochrome: bool, - // `fg_colour` must be (0, 0, 0) if `monochrome` is true. - fg_colour: RgbColour, -} -}
这是一个非常适合用包含数据的枚举替换的候选对象:
--#![allow(unused)] -fn main() { -#[derive(Debug)] -enum Colour { - Monochrome, - Foreground(RgbColour), -} - -struct DisplayProperties { - x: u32, - y: u32, - colour: Colour, -} -}
这个小例子说明了一个关键建议:使类型中的无效状态来表达无效。只支持有效值组合的类型意味着编译器会拒绝整类错误,从而产生更小、更安全的代码。
-回到枚举的强大之处,有两个概念是如此常见,以至于Rust包含了内置的枚举类型来表达它们。
-第一个概念是Option
:要么有特定类型的值(Some(T)
),要么没有(None
)。对于可能不存在的值,始终使用Option;永远不要退回到使用标记值(-1、nullptr等)来尝试表示相同的概念。
然而,还有一个微妙的要考虑的地方。如果你处理的是一组东西,你需要决定是否拥有零个东西在集合中与没有集合是相同的。对于大多数情况,这个区别并不存在,你可以直接使用Vec
然而,确实存在其他少见的情况,需要使用Option<Vec
一个常见的临界情况是可能不存在的字符串 - 使用 "" 还是 None 更合理来表示值的缺失?两种方式都可以,但 Option
第二个常见概念源自错误处理:如果一个函数失败了,该如何报告这个失败?在历史上,特殊的标记值(例如来自Linux系统调用的 -errno 返回值)或全局变量(POSIX系统的 errno)被使用。最近,支持从函数返回多个或元组返回值的语言(如Go)可能有一个约定,即在错误为非“零”时返回(结果,错误)对。
-在Rust中,总是将可能失败的操作的结果编码为 Result<T, E>。T 类型保存成功的结果(在 Ok 变体中),E 类型在失败时保存错误详情(在 Err 变体中)。使用标准类型可以清晰地表达设计意图,并允许使用标准转换(项目3)和错误处理(项目4);它还使得通过 ? 操作符来简化错误处理成为可能。
- -