pull/176/head
sunface 3 years ago
commit 798e264018

@ -84,22 +84,22 @@ pub fn add_one(x: i32) -> i32 {
#### 文档块注释`/** ... */`
与代码注释一样,文档也有块注释,当注释内容多时,可以减少`///`的使用:
```rust
````rust
/** `add_two`将指定值加2.
# Examples
\`\`\`
```
let arg = 5;
let answer = my_crate::add_two(arg);
assert_eq!(7, answer);
\`\`\`
```
*/
pub fn add_two(x: i32) -> i32 {
x + 2
}
```
````
#### 查看文档cargo doc
锦衣不夜行,这是中国人的传统美德。我们写了这么漂亮的文档注释,当然要看看网页中是什么效果咯。

@ -45,7 +45,7 @@ fn foo(x: &str) -> String {
以上场景,我们在本章将一一讲解,后面车速较快,请系好安全带。
#### 使用`Box<T>`将数据存储在堆上
如果一个变量拥有一个数值`let a = 3`, 那变量`a`必然是存储在栈上的,那如果我们想要`a`的值存储在堆上就需要使用`Boxt<T>`:
如果一个变量拥有一个数值`let a = 3`, 那变量`a`必然是存储在栈上的,那如果我们想要`a`的值存储在堆上就需要使用`Box<T>`:
```rust
fn main() {
let a = Box::new(3);
@ -114,7 +114,7 @@ error[E0072]: recursive type `List` has infinite size //递归类型`List`拥有
| ---- recursive without indirection
```
此时若想解决这个问题,就可以使用我们的`Boxt<T>`:
此时若想解决这个问题,就可以使用我们的`Box<T>`:
```rust
enum List {
Cons(i32, Box<List>),

@ -2,7 +2,7 @@
行百里者半五十,欢迎大家来到这里,虽然还不到中点,但是已经不远了。如果说之前学的基础数据类型是原子,那么本章将讲的数据类型可以认为是分子。
本章的重点在复合类型上,顾名思义,复合类型是由其它类型组合而来,最典型的就是结构体`struct`和枚举`enum`。例如一个2D的点`point(x,y)`,它从两个数值类型组合而来。我们不想单独去维护这两个数值,而是希望把它们看作一个整体去认识和处理。
本章的重点在复合类型上,顾名思义,复合类型是由其它类型组合而成的,最典型的就是结构体`struct`和枚举`enum`。例如平面上的一个点`point(x,y)`,它由两个数值类型的值`x`和`y`组合而来。我们不想单独去维护这两个数值,因为单独一个`x`或者`y`是含义不完整的,无法标识平面上的一个点,应该把它们看作一个整体去理解和处理。
来看一段代码,它使用我们之前学过的内容来构建文件操作:
```rust
@ -29,11 +29,11 @@ fn main() {
}
```
当前阶段非常类似原型设计提供api接口但是不去实现它们。因此在这个阶段我们需要排除一些编译器噪音,引入`#![allow(unused_variables)]`属性标记,该标记会告诉编译器无视未使用的变量,不要抛出`warning`警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](../../compiler/attributes.md).
接下来我们的学习非常类似原型设计有的方法只提供API接口但是不提供具体实现。此外有的变量在声明之后并未使用因此在这个阶段我们需要排除一些编译器噪音Rust在编译的时候会扫描代码变量声明后未使用会以`warning`警告的形式进行提示),引入`#![allow(unused_variables)]`属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出`warning`警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](../../compiler/attributes.md).
`read`函数也非常有趣,它返回一个`!`,这个表明该函数是一个发散函数,不会返回任何值,包括`()`。`unimplemented!()`告诉编译器该函数尚未实现,其实主要帮助我们快速完成主要代码,回头可以通过搜索这些标记来完成次要代码,类似的还有`todo!()`,当代码执行到这种未实现的地方时,程序会直接报错: 你可以反注释`read(&mut f1, &mut vec![]);`这行,然后再观察下结果。
`read`函数也非常有趣,它返回一个`!`,这个表明该函数是一个发散函数,不会返回任何值,包括`()`。`unimplemented!()`告诉编译器该函数尚未实现,`unimplemented!()`标记通常意味着我们期望快速完成主要代码,回头再通过搜索这些标记来完成次要代码,类似的标记还有`todo!()`,当代码执行到这种未实现的地方时,程序会直接报错: 你可以反注释`read(&mut f1, &mut vec![]);`这行,然后再观察下结果。
同时,从代码设计角度来看,假如关于文件操作的类型和函数散落的到处都是,是难以管理和使用的。而且`open(&mut f1)`也远没有`f1.open()`好,因此这就是基本类型的局限性:**无法从更高的抽象层次去简化代码**。
同时,从代码设计角度来看,关于文件操作的类型和函数应该组织在一起,散落得到处都是,是难以管理和使用的。而且通过`open(&mut f1)`进行调用,也远没有使用`f1.open()`来调用好,这就体现出了只使用基本类型得局限性:**无法从更高的抽象层次去简化代码**。
接下来,我们将引入一个高级数据结构 - 结构体`struct`,来看看怎样更好的解决这类问题。 开始之前,先来看看何为`元组`.
接下来,我们将引入一个高级数据结构 - 结构体`struct`,来看看复合类型是怎样更好的解决这类问题。 开始之前,先来看看什么是`元组`.

@ -35,7 +35,7 @@ Bingo果然报错了编译器提示`greet`函数需要一个`String`类型
## 切片(slice)
切片并不是Rust独有的概念在Go语言中就非常流行它允许你引用集合中一段连续的元素序列,而不是引用整个集合。
切片并不是Rust独有的概念在Go语言中就非常流行它允许你引用集合中部分连续的元素序列,而不是引用整个集合。
对于字符串而言,切片就是对`String`类型中某一部分的引用,它看起来像这样:
```rust
@ -121,7 +121,7 @@ error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immuta
回忆一下借用的规则:当我们已经有了可变借用时,就无法再拥有不可变的借用。因为`clear`需要清空改变`String`,因此它需要一个可变借用,而之后的`println!`又使用了不可变借用,因此编译无法通过。
从上述代码可以看出Rust不仅让我们的`api`更加容易使用,而且也在编译期就消除了大量错误!
从上述代码可以看出Rust不仅让我们的`API`更加容易使用,而且也在编译期就消除了大量错误!
#### 其它切片
因为切片是对集合的部分引用,因此不仅仅字符串有切片,其它集合类型也有,例如数组:
@ -348,9 +348,9 @@ for b in "中国人".bytes() {
## 字符串深度剖析
那么问题来了,为啥`String`可变,而字符串字面值却不可以?
那么问题来了,为啥`String`可变,而字符串字面值`str`却不可以?
就字符串字面值来说,我们在编译时就知道其内容,最终文本被直接硬编码进可执行文件中。这使得字符串字面值快速且高效,上述特性主要得益于字符串的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本放进内存中(你也做不到!).
就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行得过程中动态生成的。
对于 `String` 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的:
- 首先向操作系统请求内存来存放`String`对象
@ -358,9 +358,9 @@ for b in "中国人".bytes() {
其中第一个由`String::from`完成它创建了一个全新的String.
重点来了,到了第二部分,就是百家齐放的环节,在有**垃圾回收GC**的语言中GC来负责标记并清除这些不再使用的内存对象些都是自动完成无需开发者关心非常简单好用在无GC的语言开发者手动去释放这些内存对象,就像创建对象一样,需要通过编写代码来完成,因为未能正确释放对象造成的结局简直不可估量.
重点来了,到了第二部分,就是百家齐放的环节,在有**垃圾回收GC**的语言中GC来负责标记并清除这些不再使用的内存对象个过程都是自动完成无需开发者关心非常简单好用但是在无GC的语言需要开发者手动去释放这些内存对象,就像创建对象需要通过编写代码来完成一样,未能正确释放对象造成的结局简直不可估量.
对于Rust而言安全和性能是写到骨子里的核心特性使用GC牺牲了性能使用手动管理内存牺牲了安全该怎么办为此Rust的开发者想出了一个无比惊艳的办法变量在离开作用域后就自动释放其占用的内存:
对于Rust而言安全和性能是写到骨子里的核心特性如果使用GC那么会牺牲性能如果使用手动管理内存那么会牺牲安全该怎么办为此Rust的开发者想出了一个无比惊艳的办法变量在离开作用域后就自动释放其占用的内存:
```rust
{

@ -11,7 +11,7 @@
一个结构体有几部分组成:
- 通过关键字`struct`定义
- 一个清晰明确的结构体`名称`
- 数个具名的结构体`字段`
- 几个有名字的结构体`字段`
例如以下结构体定义了某网站的用户:
```rust
@ -22,7 +22,7 @@ struct User {
sign_in_count: u64,
}
```
该结构体名称是`User`拥有4个具名的字段,且每个字段都有对应的类型声明,例如`username`代表了用户名,是一个可变的`String`类型。
该结构体名称是`User`拥有4个字段且每个字段都有对应的字段名及类型声明,例如`username`代表了用户名,是一个可变的`String`类型。
#### 创建结构体实例
为了使用上述结构体,我们需要创建`User`结构体的**实例**
@ -50,7 +50,7 @@ struct User {
user1.email = String::from("anotheremail@example.com");
```
需要注意的是,必须要将整个结构体都声明为可变的,才能修改Rust不允许单独将某个字段标记为可变: `let mut user1 = User {...}`.
需要注意的是,必须要将整个结构体都声明为可变的,才能修改其中的字段Rust不允许单独将某个字段标记为可变: `let mut user1 = User {...}`.
#### 简化结构体创建
下面的函数类似一个构建函数,返回了`User`结构体的实例:
@ -98,7 +98,7 @@ fn build_user(email: String, username: String) -> User {
```
因为`user2`仅仅在`email`上与`user1`不同,因此我们只需要对`email`进行赋值,剩下的通过结构体更新语法`..user1`即可完成。
`..`语法说明我们没有显示声明的字段全部从`user1`中自动获取。需要注意的是`..user1`必须在结构体的尾部使用。
`..`语法表明凡是我们没有显示声明的字段,全部从`user1`中自动获取。需要注意的是`..user1`必须在结构体的尾部使用。
> 结构体更新语法跟赋值语句`=`非常相像,因此在上面代码中,`user1`的部分字段所有权被转移到`user2`中:`username`字段发生了所有权转移,作为结果,`user1`无法再被使用。
>
@ -165,7 +165,7 @@ println!("{:?}", user1);
从图中可以清晰的看出`File`结构体两个字段`name`和`data`分别拥有底层两个`[u8]`数组的所有权(`String`类型的底层也是`[u8]`数组),通过`ptr`指针指向底层数组的内存地址,这里你可以把`ptr`指针理解为Rust中的引用类型。
该图片也侧面印证了:把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段.
该图片也侧面印证了:**把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段**.
## 元组结构体(Tuple Struct)
@ -198,7 +198,7 @@ impl SomeTrait for AlwaysEqual {
## 结构体数据的所有权
在之前的`User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是基于引用的`&str` 字符串切片类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,而不是从其它地方借用数据。
在之前的`User` 结构体的定义中,有一处细节:我们使用了自身拥有所有权的 `String` 类型而不是基于引用的`&str` 字符串切片类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,而不是从其它地方借用数据。
你也可以让`User`结构体从其它对象借用数据,不过这么做,就需要引入**生命周期**这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。

@ -1,8 +1,8 @@
# 元组
元组是由多种类型组合到一起形成的,因此它是复合类型,元组的长度是固定的。
元组是由多种类型组合到一起形成的,因此它是复合类型,元组的长度是固定的,元组中元素的顺序也是固定的
通过以下语法可以创建一个元组:
可以通过以下语法创建一个元组:
```rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
@ -25,11 +25,11 @@ fn main() {
}
```
上述代码首先创建一个元组,然后将其绑定到`tup`上,接着使用`let (x, y, z) = tup;`来完成一次模式匹配,因为元组是`(n1,n2,n3)`形式的,因此我们用一模一样的`(x,y,z)`形式来进行匹配,然后把元组中对应的值绑定到变量`x``y``z`上,这就是解构:用同样的形式把一个复杂对象中的值匹配出来。
上述代码首先创建一个元组,然后将其绑定到`tup`上,接着使用`let (x, y, z) = tup;`来完成一次模式匹配,因为元组是`(n1,n2,n3)`形式的,因此我们用一模一样的`(x,y,z)`形式来进行匹配,元组中对应的值会绑定到变量`x``y``z`上。这就是解构:用同样的形式把一个复杂对象中的值匹配出来。
### 用`.`来访问元组
模式匹配可以让我们一次性把元组中的值全部或者部分获取出来如果想要访问某个特定元素那模式匹配就略显繁琐对此Rust提供了`.`的访问方式:
模式匹配可以让我们一次性把元组中的值全部或者部分获取出来,如果想要访问某个特定元素那模式匹配就略显繁琐对此Rust提供了`.`的访问方式:
```rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
@ -64,6 +64,6 @@ fn calculate_length(s: String) -> (String, usize) {
`calculate_length`函数接收`s1`字符串的所有权,然后计算字符串的长度,接着把字符串所有权和字符串长度再返回给`s2`和`len`变量。
对于其他语言元组可以用来声明一个3D点,例如`Point(10,20,30)`虽然使用Rust元组也可以做到`(10,20,30)`,但是这样写有个非常重大的缺陷:
在其他语言中,可以用结构体来声明一个三维空间中的点,例如`Point(10,20,30)`虽然使用Rust元组也可以做到`(10,20,30)`,但是这样写有个非常重大的缺陷:
**不具备任何清晰的含义**,在下一章节中,会提到一种`元组结构体`,可以解决这个问题。
**不具备任何清晰的含义**,在下一章节中,会提到一种与元组类似的结构体,`元组结构体`,可以解决这个问题。

Loading…
Cancel
Save