diff --git a/course-book/contents/basic/compound-type/array.md b/course-book/contents/basic/compound-type/array.md index 665e51f5..ed9935cd 100644 --- a/course-book/contents/basic/compound-type/array.md +++ b/course-book/contents/basic/compound-type/array.md @@ -19,7 +19,7 @@ fn main() { } ``` -数组语法跟`javascript`很像,也跟大多数编程语言很像。由于它的元素类型大小固定,且长度也是固定,因此**数组是存储在栈上**,性能也会非常优秀。与此对应,动态数组`Vector`是存储在堆上,因此长度可以动态改变。当你不确定是使用数组还是动态数组时,那就应该使用后者,具体见[动态数组Vector]一章. +数组语法跟`javascript`很像,也跟大多数编程语言很像。由于它的元素类型大小固定,且长度也是固定,因此**数组是存储在栈上**,性能也会非常优秀。与此对应,动态数组`Vector`是存储在堆上,因此长度可以动态改变。当你不确定是使用数组还是动态数组时,那就应该使用后者,具体见[动态数组Vector](../collections/vector.md)一章. 举个例子,在需要知道一年中各个月份名称的程序中,你很可能希望使用的是数组而不是动态数组。因为月份是固定的,它总是包含 12 个元素: ```rust @@ -35,7 +35,7 @@ let a: [i32; 5] = [1, 2, 3, 4, 5]; 还可以使用下面的语法初始化一个**某个值重复出现N次的数组**: ```rust -let a: = [3; 5]; +let a = [3; 5]; ``` `a`数组包含`5`个元素,这些元素的初始化值为`3`,聪明的读者已经发现,这种语法跟数组类型的声明语法其实是保持一致的:`[3;5]` 和`[类型;长度]`. @@ -149,7 +149,7 @@ fn main() { 做个总结,数组虽然很简单,但是其实还是存在几个要注意的点: - **数组类型容易跟数组切片混淆**,[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,因此它不具备编译器的长度,因此不能用[T;n]的形式去描述 -- `[u8; 3]`和`[u8;4]是不同的类型`,数组的长度也是类型的一部分 +- `[u8; 3]`和`[u8; 4]`是不同的类型,数组的长度也是类型的一部分 - **在实践中,使用最多的是数组切片[T]**,我们往往通过引用的方式去使用`&[T]`,因为后者有固定的类型大小. diff --git a/course-book/contents/basic/compound-type/enum.md b/course-book/contents/basic/compound-type/enum.md index b3fecd57..20668925 100644 --- a/course-book/contents/basic/compound-type/enum.md +++ b/course-book/contents/basic/compound-type/enum.md @@ -1,6 +1,6 @@ # 枚举 -枚举(enum或enumeration)允许你通过列举可能的成员来定义一个**`枚举类型`**,例如扑克牌花色: +枚举(enum或enumeration)允许你通过列举可能的成员来定义一个**枚举类型**,例如扑克牌花色: ```rust enum PokerSuit { Clubs, @@ -122,7 +122,7 @@ enum IpAddr { V6(Ipv6Addr), } ``` -该例子跟我们之前的扑克牌很像,只不过枚举成员包含的类型更复杂了,变成了结构体:分别通过`Ipv4Addr`和`Ipv4Addr`来定义两种不同的IP数据。 +该例子跟我们之前的扑克牌很像,只不过枚举成员包含的类型更复杂了,变成了结构体:分别通过`Ipv4Addr`和`Ipv6Addr`来定义两种不同的IP数据。 从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**: 例如字符串、数值、结构体甚至另一个枚举。 @@ -193,7 +193,7 @@ enum Websocket { ## Option枚举用于处理空值 在其它编程语言中,往往都有一个`null`关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整形的零值是0),也就是不存在值。当你对这些`null`进行操作时,例如调用一个方法,就会直接抛出null异常,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些`null`空值。 -> Tony Hoare,null的发明者,曾经说过有非常有名的话 +> Tony Hoare,null的发明者,曾经说过一段非常有名的话 > > 我称之为我十亿美元的错误。当时,我在使用一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过在设计过程中,我未能抵抗住诱惑,引入了空引用的概念,因为它非常容易实现。就是因为这个决策,引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。 diff --git a/course-book/contents/basic/compound-type/string-slice.md b/course-book/contents/basic/compound-type/string-slice.md index c2c4d79a..ac03833e 100644 --- a/course-book/contents/basic/compound-type/string-slice.md +++ b/course-book/contents/basic/compound-type/string-slice.md @@ -302,7 +302,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 因此在通过索引区间来访问字符串时,需要格外的小心,一不注意,就会导致你程序的崩溃! ## 操作UTF8字符串 -前文提到了几中使用UTF8字符串的方式,下面来一一说明。 +前文提到了几种使用UTF8字符串的方式,下面来一一说明。 #### 字符 如果你想要以Unicode字符的方式遍历字符串,最好的办法是使用`chars`方法,例如: @@ -342,7 +342,7 @@ for b in "中国人".bytes() { 想要准确的从UTF8字符串中获取子串是较为复杂的事情,例如想要从`holla中国人नमस्ते`这种变长的字符串中取出某一个子串,使用标准库你是做不到的, 你需要在`crates.io`上搜索`utf8`来寻找想要的功能。 -可以考虑尝试下这个库:[utf8 slice](https://crates.io/crates/utf8_slice). +可以考虑尝试下这个库:[utf8_slice](https://crates.io/crates/utf8_slice). @@ -358,7 +358,7 @@ for b in "中国人".bytes() { 其中第一个由`String::from`完成,它创建了一个全新的String. -重点来了,到了第二部分,就是百家齐放的环节,在有**垃圾回收GC**的语言中,GC来负责标记并清除这些不再使用的内存对象,这些都是自动完成,无需开发者关心,非常简单好用;在无GC的语言,是开发者手动去释放这些内存对象,就像创建对象一样,需要通过编写代码来完成,因为未能正确释放对象造成的经济简直不可估量. +重点来了,到了第二部分,就是百家齐放的环节,在有**垃圾回收GC**的语言中,GC来负责标记并清除这些不再使用的内存对象,这些都是自动完成,无需开发者关心,非常简单好用;在无GC的语言,是开发者手动去释放这些内存对象,就像创建对象一样,需要通过编写代码来完成,因为未能正确释放对象造成的结局简直不可估量. 对于Rust而言,安全和性能是写到骨子里的核心特性,使用GC牺牲了性能,使用手动管理内存牺牲了安全,那该怎么办?为此,Rust的开发者想出了一个无比惊艳的办法:变量在离开作用域后,就自动释放其占用的内存: diff --git a/course-book/contents/basic/compound-type/struct.md b/course-book/contents/basic/compound-type/struct.md index 7ab29995..7dbc8b72 100644 --- a/course-book/contents/basic/compound-type/struct.md +++ b/course-book/contents/basic/compound-type/struct.md @@ -5,7 +5,7 @@ 结构体跟之前讲过的[元组](./tuple.md)有些相像:都是由多种类型组合而成。但是与元组不同的是,结构体可以为内部的每个字段起一个富有含义的名称。因此结构体更加灵活更加强大,你无需依赖这些字段的顺序来访问和解析它们。 ## 结构体语法 -天下无敌的剑士往往也因为他有一炳无双之剑,既然结构体这么强大,那么我们就需要给它配套一套强大的语法,让用户能更好的驾驭。 +天下无敌的剑士往往也因为他有一柄无双之剑,既然结构体这么强大,那么我们就需要给它配套一套强大的语法,让用户能更好的驾驭。 #### 定义结构体 一个结构体有几部分组成: @@ -64,7 +64,7 @@ fn build_user(email: String, username: String) -> User { } } ``` -它接收两个字符串参数:`email`和`username`,然后使用它们来创建一个`User`结构体,并且返回。可以注意到这两行:`email: email`和`username: username`,非常的扎眼,因为实在有些啰嗦,如果你从typscript过来,肯定会鄙视Rust一番,不过好在,它也不是无可救药: +它接收两个字符串参数:`email`和`username`,然后使用它们来创建一个`User`结构体,并且返回。可以注意到这两行:`email: email`和`username: username`,非常的扎眼,因为实在有些啰嗦,如果你从typescript过来,肯定会鄙视Rust一番,不过好在,它也不是无可救药: ```rust fn build_user(email: String, username: String) -> User { User { @@ -184,7 +184,7 @@ struct AlwaysEqual; let subject = AlwaysEqual; // 我们不关心为AlwaysEqual的字段数据,只关心它的行为,因此将它声明为元结构体,然后再为它实现某个特征 -impl AlwaysEqual for SomeTrait { +impl SomeTrait for AlwaysEqual { } ``` @@ -194,7 +194,7 @@ impl AlwaysEqual for SomeTrait { 在之前的`User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是基于引用的`&str` 字符串切片类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,而不是从其它地方借用数据。 -你也可以让`User`结构体从其它对象借用数据,不过这么做,就需要引入**生命周期**这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要大。 +你也可以让`User`结构体从其它对象借用数据,不过这么做,就需要引入**生命周期**这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。 总之,如果你想在结构体中使用一个引用,就必须加上生命周期,否则就会报错: diff --git a/course-book/contents/basic/match-pattern/all-patterns.md b/course-book/contents/basic/match-pattern/all-patterns.md index 6a7053b3..5ae418c3 100644 --- a/course-book/contents/basic/match-pattern/all-patterns.md +++ b/course-book/contents/basic/match-pattern/all-patterns.md @@ -441,7 +441,7 @@ fn main() { 如果编译上面的例子,会得到下面的错误: ```text -error: `..` can only be used once per tuple pattern // 每个元组模式只能使用一个`..'` +error: `..` can only be used once per tuple pattern // 每个元组模式只能使用一个`..` --> src/main.rs:5:22 | 5 | (.., second, ..) => { @@ -510,7 +510,7 @@ match x { } ``` -这个匹配条件表明此分支值匹配 `x` 值为 `4`、`5` 或 `6` **同时** `y` 为 `true` 的情况。 +这个匹配条件表明此分支只匹配 `x` 值为 `4`、`5` 或 `6` **同时** `y` 为 `true` 的情况。 虽然在第一个分支中,`x`匹配了模式`4`,但是对于匹配守卫`if y`来说,因为`y`是`false`,因此该守卫条件的值永远是`false`,也意味着第一个分支永远无法被匹配. diff --git a/course-book/contents/basic/match-pattern/match-if-let.md b/course-book/contents/basic/match-pattern/match-if-let.md index bcfb9fc6..f9ec31be 100644 --- a/course-book/contents/basic/match-pattern/match-if-let.md +++ b/course-book/contents/basic/match-pattern/match-if-let.md @@ -44,6 +44,7 @@ match target { }, _ => 表达式3 } +``` 该形式清晰的说明了何为模式,何为模式匹配:将模式与`target`进行匹配,即为模式匹配,而模式匹配不仅仅局限于`match`,后面我们会详细阐述。 @@ -117,7 +118,7 @@ enum Coin { Quarter(UsState), // 25美分硬币 } ``` -其中Coin::Quarter成员还存放了一个值:美国的某个州,因为在1999年到2008年间,美国在25美分(Quater)硬币的背后为50个州印刷了不同的设计, 其它硬币都没有相关的设计。 +其中`Coin::Quarter`成员还存放了一个值:美国的某个州,因为在1999年到2008年间,美国在25美分(Quarter)硬币的背后为50个州印刷了不同的设计, 其它硬币都没有相关的设计。 接下来,我们希望在模式匹配中,获取到25美分硬币上刻印的州的名称: ```rust @@ -283,7 +284,7 @@ fn main() { 现在如果想对`v`进行过滤,只保留类型是`MyEnum::Foo`的元素,你可能想这么写: ```rust -v.iter().filter(|x| x == MyEnum:::Foo); +v.iter().filter(|x| x == MyEnum::Foo); ``` 但是,实际上这行代码会保存,因为你无法将`x`跟一个类型进行比较。好在,你可以使用`match`来完成,但是会导致代码更为啰嗦,是否有更简洁的方式?答案是使用`matches!`: @@ -301,7 +302,7 @@ assert!(matches!(bar, Some(x) if x > 2)); ``` ## 变量覆盖 -无论是是`match`还是`if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的直: +无论是是`match`还是`if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的值: ```rust fn main() { let age = Some(30); diff --git a/course-book/contents/basic/match-pattern/pattern-match.md b/course-book/contents/basic/match-pattern/pattern-match.md index c5380db3..8f8a12e2 100644 --- a/course-book/contents/basic/match-pattern/pattern-match.md +++ b/course-book/contents/basic/match-pattern/pattern-match.md @@ -11,7 +11,7 @@ ### 所有可能用到模式的地方 -#### match分支** +#### match分支 ```rust match VALUE { @@ -32,7 +32,7 @@ match VALUE { #### if let分支 `if let`往往用于匹配一个模式,而忽略剩下的所有模式的场景: ```rust -if let Pattern = SOME_VALUE { +if let PATTERN = SOME_VALUE { } ``` @@ -130,4 +130,4 @@ if let Some(x) = some_option_value { } ``` -因为`if let`允许匹配一种模式,而忽悠其余的模式。 \ No newline at end of file +因为`if let`允许匹配一种模式,而忽略其余的模式。 \ No newline at end of file diff --git a/course-book/contents/basic/method.md b/course-book/contents/basic/method.md index 955e2b8e..5c99f73a 100644 --- a/course-book/contents/basic/method.md +++ b/course-book/contents/basic/method.md @@ -68,7 +68,7 @@ fn main() { `impl Rectangle {}`表示为`Rectangle`实现方法(`impl` 是实现*implementation* 的缩写),这样的写法标明`impl`语句块中的一切都是跟`Rectangle`相关联的。 -接下里的内容非常重要,请大家仔细看。在 `area` 的签名中,有一个我们之前没有看到过的关键字`&self`,该关键字指代的是`&Rectangle`类型,换句话说,`self`指代的是`Rectangle`结构体,这样的写法会让我们的代码简洁很多,而且非常便于理解: 我们为哪个结构体实现方法,那么`self`就是指代的该结构体自身。 +接下里的内容非常重要,请大家仔细看。在 `area` 的签名中,我们使用`&self`替代`rectangle: &Rectangle`,`&self`其实是`self: &Self`的简写(注意大小写)。在一个`impl`块内,`Self`指代被实现方法的结构体类型,`self`指代此类型的实例,换句话说,`self`指代的是`Rectangle`结构体实例,这样的写法会让我们的代码简洁很多,而且非常便于理解: 我们为哪个结构体实现方法,那么`self`就是指代的该结构体的实例。 需要注意的是,`self`依然有所有权的概念: - `self`表示`Rectangle`的所有权转移到该方法中,这种形式用的较少 @@ -129,7 +129,7 @@ fn main() { } ``` -用这种方式,我们可以把`Rectangle`的字段设置为私有属性,只需把它的`new`和`witdh`方法设置为公开可见,那么用户就可以创建一个矩形,同时通过访问器`rect1.width()`方法来获取矩形的宽度, 因为`width`字段是私有的,当用户访问`rect1.witdh`字段时,就会报错。 +用这种方式,我们可以把`Rectangle`的字段设置为私有属性,只需把它的`new`和`width`方法设置为公开可见,那么用户就可以创建一个矩形,同时通过访问器`rect1.width()`方法来获取矩形的宽度, 因为`width`字段是私有的,当用户访问`rect1.width`字段时,就会报错。注意在此例中,`Self`指代的就是被实现方法的结构体`Rectangle`。 > ### `->` 运算符到哪去了? > @@ -261,4 +261,4 @@ m.call(); } ``` -除了结构体和枚举,我们还能为特征(trait)实现方法,将在下下章进行讲解,在此之前,先来看看泛型。 \ No newline at end of file +除了结构体和枚举,我们还能为特征(trait)实现方法,将在下一章进行讲解,在此之前,先来看看泛型。 \ No newline at end of file diff --git a/course-book/contents/basic/trait/advance-trait.md b/course-book/contents/basic/trait/advance-trait.md index 76c4a305..2ab6d9e5 100644 --- a/course-book/contents/basic/trait/advance-trait.md +++ b/course-book/contents/basic/trait/advance-trait.md @@ -52,7 +52,8 @@ trait Container { fn contains(&self,a: A,b: B) -> bool; } -fn difference(container: &C) -> i32 where +fn difference(container: &C) -> i32 + where C : Container {...} ``` @@ -77,7 +78,7 @@ trait Add { fn add(self, rhs: RHS) -> Self::Output; } ``` -它有一个泛型参数`RHS`,但是与我们以往的用法不同,这里它给`RHS`一个默认值,也就是当用户不指定`RHS`时,默认使用两个同样类型的值进行相加,然后返回一个关联类型`Outpu`。 +它有一个泛型参数`RHS`,但是与我们以往的用法不同,这里它给`RHS`一个默认值,也就是当用户不指定`RHS`时,默认使用两个同样类型的值进行相加,然后返回一个关联类型`Output`。 可能上面那段不太好理解,下面我们用代码来举例: ```rust @@ -196,12 +197,9 @@ fn main() { 运行后依次输出: ```console -fn main() { - let person = Human; - Pilot::fly(&person); - Wizard::fly(&person); - person.fly(); -} +This is your captain speaking. +Up! +*waving arms furiously* ``` 因为`fly`方法的参数是`self`,当显示的调用时,编译器就可以根据调用的类型(`self`的类型)决定具体调用哪个方法。 diff --git a/course-book/contents/basic/trait/generic.md b/course-book/contents/basic/trait/generic.md index 2594cd7a..e475c958 100644 --- a/course-book/contents/basic/trait/generic.md +++ b/course-book/contents/basic/trait/generic.md @@ -8,7 +8,7 @@ Go语言在2022年,就要正式引入泛型,被视为在1.0版本后,语 fn add_i8(a:i8, b:i8) -> i8 { a + b } -fn add_i32(a:i16, b:i16) -> i16 { +fn add_i32(a:i32, b:i32) -> i32 { a + b } fn add_f64(a:f64, b:f64) -> f64 { @@ -17,7 +17,7 @@ fn add_f64(a:f64, b:f64) -> f64 { fn main() { println!("add i8: {}", add_i8(2i8, 3i8)); - println!("add i16: {}", add_i32(20, 30)); + println!("add i32: {}", add_i32(20, 30)); println!("add f64: {}", add_f64(1.23, 1.23)); } ``` @@ -36,7 +36,7 @@ fn add(a:T, b:T) -> T { fn main() { println!("add i8: {}", add(2i8, 3i8)); - println!("add i16: {}", add(20, 30)); + println!("add i32: {}", add(20, 30)); println!("add f64: {}", add(1.23, 1.23)); } ``` @@ -199,10 +199,10 @@ enum Result { 该枚举和`Option`一样,主要用于函数返回值,与`Option`用于值的存在与否不同,`Result`关注的主要是正确性。 -如果函数正常运行,则最后返回一个`Ok(T)`,`T`是函数具体的返回值类型,如果函数异常运行,则返回一个`Err(E)`,`E`是具体的错误值。例如打开一个文件:当成功打开文件,返回`Ok(std::fs::File)`,因此`T` 对应的是 `std::fs::File` 类型;而当打开文件时出现问题时,返回`Err(std::io::Error)`, `E`对应的是`std::io::Error`类型。 +如果函数正常运行,则最后返回一个`Ok(T)`,`T`是函数具体的返回值类型,如果函数异常运行,则返回一个`Err(E)`,`E`是错误类型。例如打开一个文件:当成功打开文件,返回`Ok(std::fs::File)`,因此`T` 对应的是 `std::fs::File` 类型;而当打开文件时出现问题时,返回`Err(std::io::Error)`, `E`对应的是`std::io::Error`类型。 ## 方法中使用泛型 -上一节中,我们讲到何为方法以及如何在结构体和枚举上定义方法。方法上也可以使用泛型: +上一章中,我们讲到何为方法以及如何在结构体和枚举上定义方法。方法上也可以使用泛型: ```rust struct Point { x: T, @@ -268,7 +268,7 @@ impl Point { -## const泛型(Rust最新版本引入的重要特性) +## const泛型(Rust 1.51版本引入的重要特性) 在之前的泛型中,可以抽象为一句话:针对类型实现的泛型,所有的泛型都是为了抽象不同的类型,那有没有针对值的泛型?可能很多同学感觉很难理解,值怎么使用泛型?不急,我们先从数组讲起。 在[数组](../compound-type/array.md)那节,有提到过很重要的一点:`[i32; 2]`和`[i32; 3]`是不同的数组类型,例如以下代码: @@ -327,7 +327,7 @@ fn main() { } ``` -也不难,唯一要注意的是需要对T加一个限制`std::fmt::Debug`,该限制表明`T`可以用在`println!("{:?}", arr)`中,因为`{"?}`形式的格式化输出需要`arr`实现该特征。 +也不难,唯一要注意的是需要对T加一个限制`std::fmt::Debug`,该限制表明`T`可以用在`println!("{:?}", arr)`中,因为`{:?}`形式的格式化输出需要`arr`实现该特征。 通过引用,我们可以很轻松的解决处理任何类型数组的问题,但是如果在某些场景下不适宜用引用或者干脆不能用呢?那真的没什么好办法了,你们知道为什么以前Rust的一些数组库,在使用的时候都限定长度不超过32吗?因为它们会为每个长度都单独实现一个函数,简直。。。毫无人性。 @@ -356,7 +356,7 @@ fn main() { ## 泛型的性能 -在Rust中泛型是零消耗的抽象,意味着你在使用泛型时,完全不用担心性能上的问题。 +在Rust中泛型是零成本的抽象,意味着你在使用泛型时,完全不用担心性能上的问题。 但是任何选择都是权衡得失的,既然我们获得了性能上的巨大优势,那么又失去了什么呢?Rust采用的是在编译期,为泛型对应的多个类型,生成各自的代码,因此损失了编译速度和增大了最终生成文件的大小。 diff --git a/course-book/contents/basic/trait/trait-object.md b/course-book/contents/basic/trait/trait-object.md index e10952db..63f37d0e 100644 --- a/course-book/contents/basic/trait/trait-object.md +++ b/course-book/contents/basic/trait/trait-object.md @@ -217,20 +217,38 @@ fn main() { ``` 因为`String`类型没有实现`Draw`特征,编译器直接就会报错,不会让上述代码运行。如果想要`String`类型被渲染在屏幕上,那么只需要为其实现`Draw`特征即可,非常容易。 -#### &和dyn的区别 -前文提到,`&`和`dyn`都可以用于特征对象,因此在功能上`&`和`dyn`几无区别,唯一的区别就是:`&`减少了一次指针调用。 +#### &dyn和Box\的区别 +前文提到,`&dyn`和`Box`都可以用于特征对象,因此在功能上`&dyn`和`Box`几无区别,唯一的区别就是:`&dyn`减少了一次指针调用。 -因为`dyn`是一个宽指针(`fat pointer`), 它内部保存一个指针指向`vtable`,然后通过`vtable`查询到具体的函数指针,最后进行调用. +因为`Box`是一个宽指针(`fat pointer`), 它内部保存一个指针指向`vtable`,然后通过`vtable`查询到具体的函数指针,最后进行调用. -所以,如果你在乎性能,又想使用特征对象简化代码,可以优先考虑`&`。 +所以,如果你在乎性能,又想使用特征对象简化代码,可以优先考虑`&dyn`。 + +注意`dyn`不能单独作为特征对象的定义,例如下面的代码编译器会报错,原因是特征对象可以是任意实现了某个特征的类型,编译器在编译期不知道该类型的大小。 + +而`&dyn`和`Box`在编译期都是已知大小,所以可以用作特征对象的定义。 + +```rust +fn draw2(x: dyn Draw) { + x.draw(); +} +``` + +``` +10 | fn draw2(x: dyn Draw) { + | ^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `(dyn Draw + 'static)` +help: function arguments must have a statically known size, borrowed types always have a known size +``` ## 特征对象的动态分发 -回一下泛型章节我们提到过的,泛型是在编译期完成处理的:编译器会为每一个泛型参数对应的具体类型生成一份代码,这种方式是**静态分发(static dispatch)**,因为是在编译期完成的,对于运行期性能完全没有任何影响。 +回忆一下泛型章节我们提到过的,泛型是在编译期完成处理的:编译器会为每一个泛型参数对应的具体类型生成一份代码,这种方式是**静态分发(static dispatch)**,因为是在编译期完成的,对于运行期性能完全没有任何影响。 与静态分发相对应的是**动态分发(dynamic dispatch)**,在这种情况下,直到运行时,才能确定需要调用什么方法。 -当使用特赠对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于特征对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用特征对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。 +当使用特征对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于特征对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用特征对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。 ## Self与self 在Rust中,有两个`self`,一个指代当前的实例对象,一个指代特征或者方法类型的别名: @@ -264,7 +282,7 @@ fn main() { 对象安全对于特征对象是必须的,因为一旦有了特征对象,就不再知道实现该特征的具体类型是什么了。如果特征方法返回具体的`Self`类型,但是特征对象忘记了其真正的类型,那这个`Self`就非常尴尬,因为没人知道它是谁了。同理对于泛型类型参数来说,当使用特征时其会放入具体的类型参数:此具体类型变成了实现该特征的类型的一部分。当使用特征对象时其具体类型被抹去了,故而无从得知放入泛型参数类型到底是什么。 -标准库中的 `Clone`特征就符合对象安全的要求: +标准库中的 `Clone`特征就不符合对象安全的要求: ```rust pub trait Clone { diff --git a/course-book/contents/basic/trait/trait.md b/course-book/contents/basic/trait/trait.md index 85543f73..450444b0 100644 --- a/course-book/contents/basic/trait/trait.md +++ b/course-book/contents/basic/trait/trait.md @@ -123,7 +123,7 @@ impl Summary for Weibo { sunface发表了微博好像微博没Tweet好用 ``` -默认实现允许调用相同特种中的其他方法,哪怕这些方法没有默认实现。如此,特征可以提供很多有用的功能而只需要实现指定的一小部分内容。例如,我们可以定义`Summary`特征,使其具有一个需要实现的`summarize_author`方法,然后定义一个`summarize`方法,此方法的默认实现调用`summarize_author`方法: +默认实现允许调用相同特征中的其他方法,哪怕这些方法没有默认实现。如此,特征可以提供很多有用的功能而只需要实现指定的一小部分内容。例如,我们可以定义`Summary`特征,使其具有一个需要实现的`summarize_author`方法,然后定义一个`summarize`方法,此方法的默认实现调用`summarize_author`方法: ```rust pub trait Summary { fn summarize_author(&self) -> String; @@ -157,7 +157,7 @@ pub fn notify(item: &impl Summary) { } ``` -`impl Summary`,只能说出这个类型的人真的是起名鬼才,简直太贴切了,故名思义`实现了Summary特征`的`item参数. +`impl Summary`,只能说出这个类型的人真的是起名鬼才,简直太贴切了,故名思义`实现了Summary特征`的`item`参数. 你可以使用任何实现了`Summary`特征的类型作为该函数的参数,同时在函数体内,还可以调用该特征的的方法,例如`summarize`方法。具体的说,可以传递`Post`或`Weibo`的实例来作为参数,而其它类如`String`或者`i32`的类型则不能用做该函数的参数,因为它们没有实现`Summary`特征。 @@ -237,8 +237,8 @@ impl Pair { } ``` -`cmd_display`方法,并不是所有的`Pair`结构体对象都拥有,只有`T`实现了`Display + PartialOrd`的`Part`才拥有此方法 -该函数可读性会更好,因为泛型参数、参数、返回值都在一起,可以快速的阅读,同时每个泛型参数的特征也在新的代码行中通过`where`进行了约束。 +`cmd_display`方法,并不是所有的`Pair`结构体对象都拥有,只有`T`实现了`Display + PartialOrd`的`Part`才拥有此方法。 +该函数可读性会更好,因为泛型参数、参数、返回值都在一起,可以快速的阅读,同时每个泛型参数的特征也在新的代码行中通过**特征约束**进行了约束。 **也可以有条件的实现特征**, 例如,标准库为任何实现了 `Display`特征的类型实现了 `ToString`特征: ```rust @@ -390,7 +390,7 @@ fn main() { 例如`Copy`特征,它也有一套自动实现的默认代码,当标记到一个类型上时,可以让这个类型自动实现`Copy`特征,进而可以调用`copy`方法,进行自我复制。 -总是,`derive`派生出来的是Rust默认给我们提供的特征,在开发过程中极大的简化了自己手动实现相应特征的需求,当然,如果你有特殊的需求,还可以自己手动重载该实现。 +总之,`derive`派生出来的是Rust默认给我们提供的特征,在开发过程中极大的简化了自己手动实现相应特征的需求,当然,如果你有特殊的需求,还可以自己手动重载该实现。 详细的`derive`列表参加[附录-派生特征](../../appendix/derive.md).