我学Rust_1_中文教程第二版_笔记


开始看Rust的官方文档翻译:https://kaisery.github.io/trpl-zh-cn/ch01-01-installation.html
1,安装:选了windows下的,下载了 https://rustup.rs 的 rustup-init.exe
   需要安装visual cpp build tools 2015,http://landinghub.visualstudio.com/visual-cpp-build-tools
   不选择2017,因为他的编辑器试用期很短,懒得注册。
   开始安装,竟然最大是需要4Gb空间,期间还崩溃了一次。
   
2,再安装rustup-init.exe,是终端执行的。需要下载安装包。

3,下载了很多次,都失败,不是静止了就是报错了,关掉重来还得从0开始下载,看来是被墙了。
偶然的操作发现,就是不要关掉前一个终端,再双击rustup-init.exe,再关掉前一个终端,可以接着传,
熬过了最大的60mb那个,终于下载成功了,哇哈哈。

4,开始写hello world, 项目名字是小写和下划线的,
rustc --version
rustc main.rs
cargo --version
cargo new hello_cargo --bin
cd hello_cargo
cargo build
.\target\debug\hello_cargo.exe
cargo run
cargo build --release

生成的文件挺大啊,1.5mb以上,不知嵌入式如何
golang的是1.8mb以上

cargo run 命令适合用于需要快速迭代的项目

5,Rust的退格是4个空格,而不是tab符号。
变量绑定数值后默认是不可变的,可变需要加上mut 标明,
使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快。对于较小的数据结构,总是创建新实例,采用更偏向函数式的风格编程,

6,常量声明是 const MAX_POINTS: u32 = 100_000;
常量在声明它的作用域内,程序的生命周期都是有效的,
常量对后来的维护者了解值的意义很有帮助。同时将硬编码的值汇总于一处,也能为将来修改提供方便。

7,隐藏,用let 给已有的变量绑定新值,原先变量代表的值已经不可访问了,不是修改,
隐藏,可以用来复用变量名,避免不同类型的变量需要不同的名字,例如都叫spaces,而不是spaces_str 和 spaces_len

8,Rust在编译时就必须知道所有变量的类型,这是原则,如果是类似String.parse()这样的函数会解释出不确定的类型,就需要明显指定类型。
例如 let guess: u32 = "42".parse().expect("Not a number");

9,Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型
整型有 i8, u8, i6,u16,i32,u32,i64,u64,isize, usize(根据机器的位数决定长度)
       字面类型:可用_分割千分位,如100_000,这样好看些;
              0xff是十六进制,0o77是八进制,0b1111_0000是二进制,b'A'是Byte(即是u8)
 
10,浮点型有f32单精度,f64双精度,布尔型是bool,true,false,
    字符类型比较特别,是unicode标量值(unicode scalar value),
单引号括起来,但里面的可以是拼音字母,中日韩单引号括起来,但里面的可以是拼音字母,中日韩文字,emoji绘文字,空白字符。

11,复合类型:元组tuple,元素可以不同类型, let tup: (i32, f32, char) = (10_000, 3.2, 't');
              引用元素是用点加下标,如 tup.0 是 10_000, tup.1 是 3.2
 解构元组是  let (x, y, z) = tup;  x是 10_000, y 是 3.2
 
 数组array,元素是同一类型,而且长度不可变,可变的数组是vector,
 如 let ary = [1,2,3,4,5];
 引用元素是用方括号包下标,如 ary[0] 是 1, ary[2] 是 3
 数组的下标越界,Rust会panic退出程序,俗称崩溃
      这点倒是出乎我想象,我以为Rust一旦编译成功,就不会崩溃,看来对于数组,每次要预先检查下标是否会越界
 可以考虑用脚本来查找所有数组下标的代码,查看是否没有判断越界就干活。
 
12,Rust 是一个基于表达式(expression-based)的语言,这是一个需要理解的(不同于其他语言)重要区别
    语句(Statements)是执行一些操作但不返回值的指令。表达式(Expressions)计算并产生一个值。
语句并不返回值。因此,不能把 let 语句赋值给另一个变量,比如例子let x = (let y = 6);尝试做的,会产生一个错误: 
表达式计算出一些值,如 5 + 6,这是一个表达式并计算出值 11。表达式可以是语句的一部分:在列表 3-3 中有这个语句 let y = 6;,6 是一个表达式,它计算出的值是 6。
函数调用是一个表达式。宏调用是一个表达式。我们用来创建新作用域的大括号(代码块),{},也是一个表达式,如
let y = {
        let x = 3;
        x + 1
    };
是一个代码块,它的值是 4。这个值作为 let 语句的一部分被绑定到 y 上。注意结尾没有分号的那一行,与大部分我们见过的代码行不同。
表达式并不包含结尾的分号。如果在表达式的结尾加上分号,他就变成了语句,这也就使其不返回一个值。在接下来的探索中记住函数和表达式都返回值就行了。

13,函数可以向调用它的代码返回值。我们并不对返回值命名,不过会在一个箭头(->)后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。这是一个有返回值的函数的例子:
fn five() -> i32 {
5
}

14,使用过多的 else if 表达式会使代码显得杂乱无章,所以如果有多于一个 else if,最好重构代码。为此第六章会介绍 Rust 中一个叫做 match 的强大的分支结构(branching construct)。


15,因为 if 是一个表达式,我们可以在 let 语句的右侧使用它,如
let condition = true;
let number = if condition {
        5
    } else {
        6
    };
但if else里返回的类型必须一样,否则编译报错,这并不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 number 变量的类型
这样它就可以在编译时证明其他使用 number 变量的地方它的类型是有效的。

16,可以使用 for 循环来对一个集合的每个元素执行一些代码,来作为一个更有效率的替代。
let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("the value is: {}", element);
    }
for 循环的安全性和简洁性使得它在成为 Rust 中使用最多的循环结构。

17,1..10 产生数组1到10,  (1..10).rev() 产生数组1到10再反转


18,字符串字面值了,它被硬编码进程序里,是不可变的。
    String。这个类型储存在堆上所以能够储存在编译时未知大小的文本。
可以用 from 函数从字符串字面值来创建 String,如下:let s = String::from("hello");
这类字符串 可以 被修改:
    let mut s = String::from("hello");
    s.push_str(", world!"); // push_str() appends a literal to a String
    println!("{}", s); // This will print `hello, world!`


19,所有权规则:
Rust 中每一个值都有一个称之为其 所有者(owner)的变量。
值有且只能有一个所有者。
当所有者(变量)离开作用域,这个值将被丢弃。


20,let s = "hello"; 描述是变量 s 绑定到了一个字符串字面值.
Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。
当 s 离开作用域的时候。当变量离开作用域,Rust 为其调用一个特殊的函数。这个函数叫做 drop,
在这里 String 的作者可以放置释放内存的代码。Rust 在结尾的 } 处自动调用 drop。


21,如果我们 确实 需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的通用函数。
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);


22,只在栈上的数据:直接拷贝,没有发生移动。原因是像整型这样的在编译时已知大小的类型被整个储存在栈上,所以拷贝其实际的值是快速的。
这里调用 clone 并不会与通常的浅拷贝有什么不同,我们可以不用管它。


23,作为一个通用的规则,任何简单标量值的组合可以是 Copy 的,任何需要分配内存,或者本身就是某种形式资源的类型不会是 Copy 的。
元组,当且仅当其包含的类型也都是 Copy 的时候。(i32, i32) 是 Copy 的,不过 (i32, String) 就不是。


24,将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。
例如把字符串变量s给函数做参数,s就被移动了不再有效,不能再被调用,但把整型i给函数做参数,因整型i用了copy特性,所以i后面还是能被调用的


25,返回值也可以转移作用域。
变量的所有权总是遵循相同的模式:将值赋值给另一个变量时移动它。
当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。


26,fn calculate_length(s: &String) -> usize { s.len() }
函数使用引用而不是实际值作为参数意味着无需返回值来交还所有权,因为就不曾拥有所有权。
正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。


27,想修改引用的值,可以如下:
fn main() {
    let mut s = String::from("hello");


    change(&mut s);
}


fn change(some_string: &mut String) {
    some_string.push_str(", world");
}
必须将 s 改为 mut。然后必须创建一个可变引用 &mut s 和接受一个可变引用,some_string: &mut String。


28,可变引用有一个很大的限制:在特定作用域中的特定数据有且只有一个可变引用。这些代码会失败:
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
限制的好处是 Rust 可以在编译时就避免数据竞争。
可以使用大括号来创建一个新的作用域来允许拥有多个可变引用,只是不能 同时 拥有:
let mut s = String::from("hello");
{
let r1 = &mut s;//r1离开这个括号就被drop了,不会和下面的r2共存在同一个作用域里
}
let r2 = &mut s;


29,当结合可变和不可变引用时有一个类似的规则存在。这些代码会导致一个错误:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在它的眼皮底下值突然就被改变了!
然而,多个不可变引用是没有问题的因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。


30,在任意给定时间,只能 拥有如下中的一个:
一个可变引用。
任意数量的不可变引用。
引用必须总是有效的。


31,字符串 slice(string slice)是 String 中一部分值的引用,它看起来像这样:
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];


这类似于获取整个 String 的引用不过带有额外的 [0..5] 部分。不同于整个 String 的引用,这是一个包含 String 内部的一个位置和所需元素数量的引用。
start..end 语法代表一个以 start 开头并一直持续到但不包含 end 的 range。


使用一个由中括号中的 [starting_index..ending_index] 指定的 range 创建一个 slice,其中 starting_index 是包含在 slice 的第一个位置,ending_index 则是 slice 最后一个位置的后一个值。
在其内部,slice 的数据结构储存了开始位置和 slice 的长度,长度对应 ending_index 减去 starting_index 的值。所以对于 let world = &s[6..11]; 的情况,world 将是一个包含指向 s 第 6 个字节的指针和长度值 5 的 slice。


32,“字符串 slice” 的类型签名写作 &str
fn first_word(s: &String) -> &str {}
更有经验的 Rustacean 会写下如下这一行
fn first_word(s: &str) -> &str {}
如果有一个字符串 slice,可以直接传递它。如果有一个 String,则可以传递整个 String 的 slice。定义一个获取字符串 slice 而不是字符串引用的函数使得我们的 API 更加通用并且不会丢失任何功能:


33,其他类型的 slice
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
这个 slice 的类型是 &[i32]。可以对其他所有类型的集合使用这类 slice。讲到 vector 时会详细讨论这些集合。


34,因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 字段初始化简写语法(field init shorthand)来重写 build_user,这样其行为与之前完全相同,不过无需重复 email 和 username 了,如示例 5-5 所示。
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}


35,从老的对象创建新的对象常常是很有帮助的,即复用大部分老对象的值并只改变一部分值。
这可以通过 结构体更新语法(struct update syntax)实现。
使用结构体更新语法,我们可以通过更少的代码来达到相同的效果,如示例 5-7 所示。
.. 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。
let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};


36,元组结构体在你希望命名整个元组并使其与其他(同样的)元组为不同类型时很有用,这时像常规结构体那样为每个字段命名就显得冗余和形式化了。
定义元组结构体以 struct 关键字和结构体名开头并后跟元组中的类型。例如,这里是两个分别叫做 Color 和 Point 元组结构体的定义和用例:
struct Point(i32, i32, i32);
let origin = Point(0, 0, 0);


37,不同的元组结构体的实例是不能通用的,即使结构体中的字段有着相同的类型。
其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用 . 后跟索引来访问单独的值,等等。


38,在struct 定义上一行添加#[derive(Debug)], 打印时候使用 {:#?} 可以打印出有格式的log,如
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
println!("长方形是{:#?}", rectangle);


39,定义于 Rectangle 结构体上的 area 方法,
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
注意仍然需要在 self 前面加上 &,就像 &Rectangle 一样。
方法可以选择获取 self 的所有权,或者像我们这里一样不可变地借用 self,或者可变地借用 self,就跟其他别的参数一样。
这里选择 &self 跟在函数版本中使用 &Rectangle 出于同样的理由:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。
如果想要在方法中改变调用方法的实例,需要将第一个参数改为 &mut self。
通过仅仅使用 self 作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 self 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。


40,使用方法替代函数,除了使用了方法语法和不需要在每个函数签名中重复 self 类型之外,其主要好处在于组织性。我们将某个类型实例能做的所有事情都一起放入 impl 块中,而不是让将来的用户在我们的库中到处寻找 Rectangle 的功能。


41,当使用 object.something() 调用方法时,Rust 会自动增加 &、&mut 或 * 以便使 object 符合方法的签名。也就是说,这些代码是等价的:
p1.distance(&p2);
(&p1).distance(&p2);
第一行看起来简洁的多。这种自动解引用的行为之所以能行得通是因为方法有一个明确的接收者———— self 类型。在给出接收者和方法名的前提下,Rust 可以明确地计算出方法是仅仅读取(&self),做出修改(&mut self)或者是获取所有权(self)。
Rust 这种使得借用对方法接收者来说是隐式的做法是其所有权系统程序员友好性实践的一大部分。


42,impl 块的另一个有用的功能是:允许在 impl 块中定义 不 以 self 作为参数的函数。这被称为 关联函数(associated functions),因为它们与结构体相关联。即便如此它们仍是函数而不是方法,因为它们并不作用于一个结构体的实例。我们已经使用过 String::from 关联函数了。
关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且同时用来作为宽和高,这样可以更轻松的创建一个正方形 Rectangle 而不必指定两次同样的值:


43,结构体让我们可以在自己的范围内创建有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。


44,
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
可以将任意类型的数据放入枚举成员中:例如字符串、数字类型或者结构体。甚至可以包含另一个枚举
Move 包含一个匿名结构体
ChangeColor 包含三个 i32。


45,结构体和枚举还有另一个相似点:就像可以使用 impl 来为结构体定义方法那样,也可以在枚举上定义方法。


46,当在 Rust 中拥有一个像 i8 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需判空。只有当使用 Option<i8>(或者任何用到的类型)的时候需要担心可能没有一个值,
而编译器会确保我们在使用值之前处理为空的情况。
换句话说,在对 Option<T> 进行 T 的运算之前必须将其转换为 T。通常这能帮助我们捕获空值最常见的问题之一:假设某值不为空但实际上为空的情况。


47,无需担心错过存在非空值的假设让我们对代码更加有信心,为了拥有一个可能为空的值,必须显式的将其放入对应类型的 Option<T> 中。
接着,当使用这个值时,必须明确的处理值为空的情况。任何地方一个值不是 Option<T> 类型的话,可以 安全的假设它的值不为空。
这是 Rust 的一个有意为之的设计选择,来限制空值的泛滥和增加 Rust 代码的安全性。


48,为了使用 Option<T> 值,需要编写处理每个成员的代码。我们想要一些代码只当拥有 Some(T) 值时运行,这些代码允许使用其中的 T。
也希望一些代码在 None 值时运行,这些代码并没有一个可用的 T 值。match 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。


49,匹配是穷尽的,必须穷举到最后的可能性来使代码有效。
Rust 也提供了一个模式用于不想列举出所有可能值的场景。例如,u8 可以拥有 0 到 255 的有效的值,如果我们只关心 1、3、5 和 7 这几个值,就并不想必须列出 0、2、4、6、8、9 一直到 255 的值。所幸我们不必这么做:可以使用特殊的模式 _ 替代:


50,if let Some(3) = some_u8_value {
    println!("three");
}
if let 获取通过 = 分隔的一个模式和一个表达式。
使用 if let 意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 match 强制要求的穷尽性检查。
match 和 if let 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。


51,可以在 if let 中包含一个 else。else 块中的代码与 match 表达式中的 _ 分支块中的代码相同,这样的 match 表达式就等同于 if let 和 else。




模块:
1,Cargo 默认会创建一个库 crate 除非指定其他项目类型,所以如果不像一直以来那样加入 --bin 参数则项目将会是一个库
cargo new communicator


2,总结一下与文件有关的模块规则:


如果一个叫做 foo 的模块没有子模块,应该将 foo 的声明放入叫做 foo.rs 的文件中。
如果一个叫做 foo 的模块有子模块,应该将 foo 的声明放入叫做 foo/mod.rs 的文件中。


3,Rust 所有代码的默认状态是私有的:除了自己之外别人不允许使用这些代码。
如果不在自己的项目中使用一个私有函数,因为程序自身是唯一允许使用这个函数的代码,Rust 会警告说函数未被使用。


4,pub标记为公有让 Rust 知道了函数将会在程序的外部被使用。现在这个可能的理论上的外部可用性使得 Rust 认为这个函数 “已经被使用”。
因此,当某项被标记为公有,Rust 不再要求它在程序自身被使用并停止警告函数未被使用。


5,总的来说,有如下项的可见性规则:


如果一个项是公有的,它能被任何父模块访问
如果一个项是私有的,它能被其直接父模块及其任何子模块访问


5,总的来说,有如下项的可见性规则:
如果一个项是公有的,它能被任何父模块访问
如果一个项是私有的,它能被其直接父模块及其任何子模块访问


6,因为枚举也像模块一样组成了某种命名空间,也可以使用 use 来导入枚举的成员。对于任何类型的 use 语句,如果从一个命名空间导入多个项,可以在最后使用大括号和逗号来列举它们,像这样:


enum TrafficLight {
    Red,
    Yellow,
    Green,
}


use TrafficLight::{Red, Yellow};


fn main() {
    let red = Red;
    let yellow = Yellow;
    let green = TrafficLight::Green;
}
我们仍然为 Green 成员指定了 TrafficLight 命名空间,因为并没有在 use 语句中包含 Green。


7,use TrafficLight::*;
fn main() {
    let red = Red;
    let yellow = Yellow;
    let green = Green;
}
* 会将 TrafficLight 命名空间中所有可见的项都引入作用域。请保守的使用 glob:它们是方便的,但是也可能会引入多于预期的内容从而导致命名冲突。


8,路径是相对于当前模块的,在这里就是 tests。唯一的例外就是 use 语句,它默认是相对于 crate 根模块的。我们的 tests 模块需要 client 模块位于其作用域中!


9,::client::connect(); 想要从根模块开始并列出整个路径
super::client::connect(); 使用 super 在层级中上移到当前模块的上一级模块


在每一个测试中总是不得不编写 super:: 也会显得很恼人
在 tests 模块,use super::something 是常用的手段


10, for c in "नमस्ते".chars() {
    println!("{}", c);
}
打印出:








for b in "नमस्ते".bytes() {
    println!("{}", b);
}
打印出:
224
164
168
224
// ... etc
请记住有效的 Unicode 标量值可能会由不止一个字节组成。




11,同样类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。




12,另一个构建哈希 map 的方法是使用一个元组的 vector 的 collect 方法,其中每个元组包含一个键值对。collect 方法可以将数据收集进一系列的集合类型,包括 HashMap。
例如,如果队伍的名字和初始分数分别在两个 vector 中,可以使用 zip 方法来创建一个元组的 vector,其中 “Blue” 与 10 是一对,依此类推。
接着就可以使用 collect 方法将这个元组 vector 转换成一个 HashMap,如示例 8-21 所示:
#![allow(unused_variables)]
fn main() {
use std::collections::HashMap;


let teams  = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];


let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
}


13,let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);


scores.entry(String::from("Yellow")).or_insert(50);//Entry 的 or_insert 方法在键对应的值如果不存在则将参数作为新值插入并返回修改过的 Entry。
scores.entry(String::from("Blue")).or_insert(50);//Entry 的 or_insert 方法在键对应的值存在时就返回这个值的 Entry,
运行示例 8-25 的代码会打印出 {"Yellow": 50, "Blue": 10}。第一个 entry 调用会插入黄队的键和值 50,因为黄队并没有一个值。第二个 entry 调用不会改变哈希 map 因为蓝队已经有了值 10。


14,
use std::collections::HashMap;


let text = "hello world wonderful world";


let mut map = HashMap::new();


for word in text.split_whitespace() {
    let count = map.entry(word).or_insert(0);
    *count += 1;
}


println!("{:?}", map);
这会打印出 {"world": 2, "hello": 1, "wonderful": 1},or_insert 方法事实上会返回这个键的值的一个可变引用(&mut V)。
这里我们将这个可变引用储存在 count 变量中,所以为了赋值必须首先使用星号(*)解引用 count。这个可变引用在 for 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。






错误处理
1,如果你需要项目的最终二进制文件越小越好,可以由 panic 时展开切换为终止,通过在 Cargo.toml 的 [profile] 部分增加 panic = 'abort'。例如,如果你想要在发布模式中 panic 时直接终止:
[profile.release]
panic = 'abort'




2,让我们在一个简单的程序中调用 panic!:
文件名: src/main.rs
fn main() {
    panic!("crash and burn");
}


3,运行出错,打印出调用栈的命令是:RUST_BACKTRACE=1 cargo run
为了获取带有这些信息的 backtrace,必须启用 debug 标识。当不使用 --release 参数运行 cargo build 或 cargo run 时 debug 标识会默认启用,这里便是如此。


4,如何从错误中恢复:
use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
}
File::open函数返回的应该是std::result::Result类型,但这类型是个泛型
enum Result<T, E> {
    Ok(T),
    Err(E),
}
你可能调用的时候不知道T是什么,E是什么
可以故意给f一个类型例如u32,编译器会报错告诉你对应的类型是什么
let f: u32 = File::open("hello.txt");
编译器报错是:
expected type `u32`
found type `std::result::Result<std::fs::File, std::io::Error>`
你就知道T是std::fs::File是一个文件句柄, E是std::io::Error


File::open 需要一个方式告诉我们是成功还是失败,并同时提供给我们文件句柄或错误信息。而这些信息正是 Result 枚举可以提供的。


5,
use std::fs::File;


fn main() {
    let f = File::open("hello.txt");


    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("There was a problem opening the file: {:?}", error)
        },
    };
}


thread 'main' panicked at 'There was a problem opening the file: Error { repr:
Os { code: 2, message: "No such file or directory" } }', src/main.rs:9:12


6,
use std::fs::File;
use std::io::ErrorKind;


fn main() {
    let f = File::open("hello.txt");


    let f = match f {
        Ok(file) => file,
        Err(ref error) if error.kind() == ErrorKind::NotFound => {
            match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => {
                    panic!(
                        "Tried to create file but there was a problem: {:?}",
                        e
                    )
                },
            }
        },
        Err(error) => {
            panic!(
                "There was a problem opening the file: {:?}",
                error
            )
        },
    };
}
& 匹配一个引用并返回它的值,而 ref 匹配一个值并返回一个引用


7,use std::fs::File;


fn main() {
    let f = File::open("hello.txt").unwrap();
}




use std::fs::File;


fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}




8,use std::io;
use std::io::Read;
use std::fs::File;


fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}


9,
use std::io;
use std::io::Read;
use std::fs::File;


fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();


    File::open("hello.txt")?.read_to_string(&mut s)?;


    Ok(s)
}


10,选择返回 Result 值的话,就将选择权交给了调用者,而不是代替他们做出决定。
示例、代码原型和测试都非常适合 panic


11,panic! 通常适合调用不能够控制的外部代码时,这时无法修复其返回的无效状态。
无论代码编写的多么好,当有害状态是预期会出现时,返回 Result 仍要比调用 panic! 更为合适。
这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。
在这些例子中,应该通过返回 Result 来表明失败预期是可能的,这样将有害状态向上传播,这样调用者就可以决定该如何处理这个问题。
使用 panic! 来处理这些情况就不是最好的选择。


12,当代码对值进行操作时,应该首先验证值是有效的,并在其无效时 panic!。
这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会 panic! 的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。
函数通常都遵循 契约(contracts):他们的行为只有在输入满足特定条件时才能得到保证。
当违反契约时 panic 是有道理的,因为这通常代表调用方的 bug,而且这也不是那种你希望调用方必须处理的错误。
事实上也没有合理的方式来恢复调用方的代码:调用方的 程序员 需要修复其代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中得到解释。


13,trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。
类似于接口
pub trait Summerizable {
    fn summary(&self) -> String;//要求实现
}


trait的summary方法在定义的时候可以带默认实现,
pub trait Summarizable {
    fn summary(&self) -> String {//不要求实现
        String::from("(Read more...)")
    }
}


示例 
impl Summerizable for struct_name {
    fn summary(&self) -> String {
   format!("hello world, {}", self.name)
}
}


impl Summarizable for NewsArticle {} //调用的时候会使用默认实现


14,trait 实现的一个需要注意的限制是:只能在 trait 或对应类型位于我们 crate 本地的时候为其实现 trait。换句话说,不允许对外部类型实现外部 trait。
例如,不能在 Vec 上实现 Display trait,因为 Display 和 Vec 都定义于标准库中。
允许在像 Tweet 这样作为我们 aggregatorcrate 部分功能的自定义类型上实现标准库中的 trait Display。
也允许在 aggregatorcrate 中为 Vec 实现 Summarizable,因为 Summarizable 定义于此。
这个限制是我们称为 孤儿规则(orphan rule)的一部分,如果你感兴趣的可以在类型理论中找到它。
简单来说,它被称为 orphan rule 是因为其父类型不存在。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,因而这两个实现会相互冲突:Rust 将无从得知应该使用哪一个。
因为 Rust 强制执行 orphan rule,其他人编写的代码不会破坏你代码,反之亦是如此。




15,每一个test是不同的线程在运行的,所以不会互相影响。
貌似test运行的顺序是按函数名来排序的,a的排前面,哈哈
可以使用cargo test 函数名字或函数名里包括的字符串,
例如cargo test fail_test ,  cargo会测试所有包括fail_test的测试,


cargo test 生成的二进制文件的默认行为是并行的运行所有测试,并捕获测试运行过程中产生的输出避免他们被显示出来,使得阅读测试结果相关的内容变得更容易。


16,回忆一下第十章 “静态生命周期” 中讲到 &'static str 是字符串字面值的类型,也是目前的错误信息。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值