昨天我们学了rust
中的一些高级特性
今天本来是要开始实现最终的项目的,但是想了下先看下目录里的内容再来做应该会好些。
有些内容到时候忘了回来看下这个附录或许会有所帮助。
这些关键字是函数、变量、参数、结构体的字段、模块、crates
、常量、宏、静态值、属性、类型、traits
以及生命周期的名字。
由于知乎的编辑器的功能比较简单,这里就直接用Typora
转成图片输出了。
目前有些字段是没有实用性的,但是确实是存在rust
中的,因为将来可能会用到。
raw indentifier
)关键字一般是不允许在不符合要求的地方使用的,比如你的函数名叫做match
,这样是不允许的。
这会导致抽象语法树构建失败,因为你这个关键字用错地方,状态机解析文件会失败。
但有的人就是头铁,就是要用。
rust
其实允许我们通过r#
这个前缀来使用这些关键字。
比如:
这样就能正常使用了,但是实际上这么做和改个名字又有何区别呢。。。都是为了让文件解析不会出错。
实际上还是有的,因为有的时候是因为一些标准的变化导致某个词突然变成关键字,比如try
在2015不是关键字但是到了2018
就变成关键字了,这个时候就难受。
所以为了避免这种问题,如果你觉得你用的某个词很有可能在将来变成一个关键字,那你就加上r#
,这样就能确保将来标准变了之后也不会有事。
如果直接使用match
会导致下面的报错
Operator
)和符号(Symbol
)附录B包含一些rust
的语法术语,包含运算符和一些特殊的符号,它们可能出现在上下文环境中,比如泛型、trait bounds
、宏、属性、注释、元组、括号等。
img_operators
表格有些丑,因为typora
缩小再导出表格图片会有覆盖的bug
,所以这里就没有缩小。
突然发现知乎可以上传文档,解析出来的表格还行,但是想了下还是放图片算了。
重载是我们之前学的知识,可以通过实现std::ops
里的trait
来自定义操作符的功能。
忘了的同学直接不及格~
non-operator symbols
)非运算的符号,它们不像函数/方法调用。
derivable
)trait
之前我们有用过derive
这个属性,比如#[derive(Debug)]
用在struct/enum
上。
学完宏这一章节之后,我们知道了这其中的原理,虽然仅仅只是浅浅的学了表层。
它是一种派生宏,它会自动给这个struct/enum
实现std::fmt::Debug
这个trait
。
不过Debug
只是标准库中其中一种派生属性和trait
,接下来我们来看下标准库中还有哪些。
每一栏都包含下面几点
trait
;trait
能干啥trait
对于这个类型来说意味着什么trait
的条件只有这些由标准库提供的trait
能通过派生宏给你的类型实现,其它都不行。
其中一个例子就是Display
这个trait
,它可以用于格式化输出内容,但是它不能通过derive
来实现。
我们的附录不会有太多全面的内容,所以如果你想了解更多,可以直接看这个文档
std - Rustdoc.rust-lang.org/std/index.html
Debug
老熟人了,可以让我们的数据格式化为字符串的形式并输出到终端。比如使用{:?}
或者dbg!
。
其他就不都说了,例子的话,还记得我们的assert_eq!
这个宏吗?它就是实现了这个trait
,我们可以给这个宏传入第二第三个参数,这样在fail
的时候就可以输出我们想要的错误提示语。
这好像有些跑题。。。。
我们来搞个struct
来通过派生宏实现这个Debug
partialEq
和Eq
用于等价比较PartialEq
这个trait
允许我们比较两个类型的实例,判断它们是否相等,并且可以使用==
和!=
这两个运算符。
它会帮你实现eq
这个方法,当你的struct
实现了这个trait
,他就能被用于比较两个实例是否字段都相等。而如果是enum
,每一个变体都等于它自己并且不等于其它变体。
搞个例子,还是assert_eq!
这个宏,因为它有需要比较两个实例的功能需求,所以这个宏内部实现了这个trait
。
而Eq
这个trait
没有方法,它存在的目的就是为了表示这个值它等价于自己。
这个trait
的高阶trait
是PartialEq
,也就是说你要实现Eq
这个trait
之前你得先实现PartialEq
。
这样就能用=
或者!=
运算符比较自己了,比如HashMap
,它就能比较两个key
值是否相同。
当然,反过来并不行,并不是实现了PartialEq
的都能实现Eq
。
比如浮动数字类型的其中一种数据NaN
,它就没办法等价于自己。
官方给的例子都是口述的,醉了,附录就是这么没有面子
我们来写一个例子实现这俩trait
。
可以看到我们使用==
并没有报错。
PartialOrd
和Ord
用于排序比较PartialOrd
这个trait
允许你基于排序的目的去比较同一个类型的两个实例。
如果一个类型实现了PartialOrd
这个trait
,那么它就能使用<, >, <=, >=
这些比较运算符。
不过这玩意儿的高阶trait
也是PartialEq
,没想到吧。。。也就是说只有先实现了PartialEq
才有资格实现PartialOrd
。
它会实现一个partial_cmp
的方法,这个方法的返回值是一个Option
的类型。
如果你提供的值无法进行排序,那么它会返回一个None
。只要你的数据中存在一个无法比较的比如NaN
,这个时候它就会返回None
。
当然,struct
自然也是能比较的,它会比较你这俩struct
的每一个字段的值。
而对于enum
,则是比较这些变体的定义顺序,比如第一个变体小于最后一个变体。
搞个例子,比如rand
这个crate
里的 gen_range
方法,它基于一个range
范围来生成一个随机数。
而Ord
这个trait
允许你注释的那个类型的两个值用于排序。它实现了cmp
这个trait
,返回的类型是Ordering
,而不是Option
。
而它的高阶trait
是上面仨货PartialEq
、Eq
、PartialOrd
。
当实现的对象是struct/enum
的时候,cmp
这个方法的表现是和PartialOrd
的partial_cmp
一样的。
其中一个使用Ord
的例子是BTreeSet
这个数据结构,树结构自然要排序。
我们来写个例子
如果不派生PartialOrd
的话是没办法使用<
的,会报错。
Clone
和Copy
用于复制值Clone
这个trait
其实我们之前有说到过,在说堆和栈内存的那一章。
它允许我们深复制堆(heap
)内存上的值而不是创建一个新的指针。
派生Clone
会实现clone
这个方法,它会应用于实现这个trait
的类型的每一处。也就是说它会帮你的字段/变体/值等也实现派生这个Clone
。
Clone
的一个例子就是切片的to_vec
方法。切片大家都知道,没有数据所有权,而对于一个vector
来说,它又需要元素的所有权,所以正常情况下它是无法装换成vector
的,但是基于Clone
这个trait
,可以直接复制一份堆内存上的数据出来放到一个新的vector
里面。
而Copy
则是相当于浅复制,它只能用于复制stack
也就是栈空间的数据。你不需要去写任何的代码。
Copy
这个trait
没有method
,因为太过重要了,所以没有任何的方法暴露出来,这样开发者就没办法去重载这个trait
的方法。通过Copy
来复制数据是非常快的,当然,你这个数据得是放在stack
上的。
如果你的类型的所有部分都实现了Copy
这个trait
,那你就能派生这个trait
给你的这个类型。
Copy
的高阶trait
是Clone
,因为Copy
里有些功能和Clone
的一样。
这个trait
挺少地方会用到。
并且这个trait
是隐式的,你并不需要去调用clone
这个方法来复制值。
我们来写个例子
我的例子没有Copy
,是因为String
没有实现Copy
导致name
这个字段没有Copy
导致没法派生Copy
。
Hash
把一个值转换成另一个固定大小的值Hash
这个trait
允许你把任何大小的类型的实例map
成一个固定大小的值,基于hash
这个函数。
所以派生Hash
会实现hash
这个方法。由于hash
这个方法会应用于这个类型的所有地方,所以所有的字段或者类型也都会跟着派生Hash
。
举个例子,这个trait
会被用于HashMap
中的K
。
我们来写个例子
Default
来设置默认值Default
这个trait
看名字就知道是允许你给你的类型设置一个默认值,这个看起来还挺好用的。
派生它会实现default
这个方法。
同样地,它会为你的类型的每一处地方派生这个trait
。
它比较常见的地方是结构体的更新,如果两个结构体的类型一样的话,可以..
拓展另一个结构体的数据到这个结构体内。
注意和前端的...
有点区别,就是不会覆盖操作。
当我们创建一个struct
实例的时候我们直接就调用..default
来赋值,这样就很舒服。
另外它也被使用于unwrap_or_default
这个方法中,它是Option
的方法。
如果你的类型有派生这个trait
的话,那他就会使用这个default
方法在这个值是None
的时候,当然,没有实现则直接返回一个空元组。
我们来写个例子
总所周知,工欲善其事必先利其器,好的语言离不开好的工具辅助。
rustfmt
:自动格式化貌似是自带的,如果你没有这个工具的话可以执行下面的指令
安装完之后在终端输入
然后你的代码就格式化好了。
rustfix
它是安装的时候就自带的了,然后它可以自动修复编译器警告的代码。
我们直接来试下
这段代码会被警告
i
这个变量并没有被使用。
我们来运行下修复指令
然后会自动被调整为
吐槽:我终于知道_xx
用来忽略无使用变量的场景了。。。。 感情是给修复工具用的。
Clippy
类似ESLint
,所以是干嘛的不用多说了。
我们来安装下,貌似也是自带的。
搞个例子
这段代码咋一看没啥错误
但是当我们运行cargo clippy
之后就报错了
告诉我们要使用consts::PI
来代替。
rust-analyzer
这货就不多说了,如果你的IDE
还没这货建议你赶紧安装。
我们通过cargo new
创建的项目里的Cargo.toml
里的package
这一栏自带了一个edition
字段,它是版本的意思,我们来稍微深入了解下。
Rust
和它的编译器更新迭代是六周一次(浑身是肝)。当然,更新的频率上去了更新的内容自然就少了很多,所以大多都是小版本。截止至文档编写阶段,最新版本是1.31
。
大概两到三年,rust
开发团队会推出一个新的edition
。 期间小版本的内容都会汇聚到了这个新的edition
中,包括文档和工具。
我们可以通过edtion
这个字段来控制当前项目rust
的core
版本,看情况需要使用哪个版本。
截止至文档编写时间,edtion
有三个版本, Rust2015/2018/2021
。
初始化的项目都是2021
,如果你手动去创建一个项目并且Cargo.toml
没有配置edition
,那么你这个项目就会使用2015
的版本。
单独升级编译器的版本是不会让你的代码打包出来的东西有变化的,除非你改动edition
。
编译器适配任何的版本,并且你引入的crate
也是适配的。
如果包的edition
和项目的edtion
不同又会是怎么样呢?。也是没问题的,比如2015
版本的crate
同样可以用于2018
的项目edition
,看来是向下兼容的。
另外,新特性大部分都是应用于所有的edition
的,所以不用改动edition
的版本也是可以享受到新特性的。
不过也有一小部分不行,比如一个新的keyword
被创造出来,这个时候如果你有需要使用你就得升级版本了。
我们就理解到这里,想了解更多的话可以看这个文档
The Rust Edition Guidedoc.rust-lang.org/stable/edition-guide/
这个就不多说了,这里就只放简体中文的
https://github.com/KaiserY/trpl-zh-cngithub.com/KaiserY/trpl-zh-cn
想要看其它版本的点下注释22
跳过去即可。
Rust
这门语言是如何实现的以及Nightly Rust
stability without stagnation
)作为一门语言,Rust
考虑最多的点就是稳定。
Rust
的开发团队希望rust
的基础稳定可靠,这样开发者们使用起来就很安全了。
但是如果某些东西是一直变化着的,那这stability
就几乎不可能了。
同时,如果开发团队没有先测试过新特性就发布的话,那等到发布后再想去修复就不可能了。
针对于以上两个问题,rust
提出的解决方案就做stability without stagnation
。
它的指导理念是:不用害怕更新版本。
Choo, Choo! Release Channels and Riding the Trains
这标题我真不知道咋翻译。。。作为一个英语渣,我感到很惭愧。
Rust
的开发类似操作列车时刻表,所有的开发都在master
也就是主分支上完成。
发布跟随一个软件发布火车模型(train model
)(目前使用这个模型的还有Cisco IOS
和其它软件项目)。
rust
有三种发布渠道:
Nightly
Beta
Stable
大部分开发者们使用的都是stable
这个渠道的,如果你想尝试新特性,你可以选择nightly
或者beta
的渠道。
如果master
分支上有人提交了代码,那么就说明有一个新特性出现了。
每一个晚上(?),都有一个nightly
的版本会被发布。所以每一天都是发布日,而这些发布的版本由发布的设施自动发布。
每隔六周左右都准备发布一个非nightly
的beta
版本。Beta
的分支会从master
拉代码。
Beta
是测试版本,大部分开发者都不会使用到这个渠道的版本,不过对于爱好者来说可以用来测试新特性的bug
。
如果有bug
被发现了之后,开发团队就会补commit
到master
分支,然后打补丁到beta
分支上,这样beta
分支就更新一次版本。
然后再过六周,我们就可以基于beta
最后一次迭代代码拉到stable
分支上。
那么至此,就多了一个新的稳定版本,而这个版本也是大多数开发者们使用的。
这看起来像不像火车?
每隔六周,有一趟列车从nightly
发车到beta
,但是还得继续往前走一段时间直到可以发布稳定版本后。
unstable features
)不稳定特性也是发布模型的相关部分。
rust
开发团队使用一种技术叫feature flags
来决定哪些特性可以发布。如果一个特性处于活跃的开发阶段,那么它就会被添加到master
分支(在feature flag
的后面)。
如果你想使用这个还处于开发阶段的特性,你可以使用nightly
渠道的rust
版本并且在你的代码中注释这个特性flag
。除此之外没有任何的渠道可以使用这个新特性。
另外,这本书中提到的所有内容都是稳定版本的内容。
rustup
和Nightly Rust
扮演的角色默认情况我们是stable
的渠道。
我们可以基于rustup
快速的切换到其它渠道 ,比如切换到nightly
渠道。
不过在这之前,我们需要先安装渠道。
另外你还可以使用toolchain
指令列出你的电脑中的所有渠道的各个版本
可以看到我们默认是stable
,如果你追逐先进的特性,那么你可以修改渠道。
注意这里是覆盖当前项目的渠道,而不是全局的。
RFC
流程(Process
)和团队(Teams
)RFC
指的是Request For Comments
也就是广纳谏。
如果你对rust
有一些特别的想法,你可以到官网去发布你的想法,然后开发团队会审查讨论你这个想法。
官网有很多子团队,根据不同的功能来区分的,你可以到这里看下
https://www.rust-lang.org/governance
如果你的想法被接纳了,那就会在rust
仓库里创建一个issue
。
然后会有人去实现它。当它被实现了之后,它就会被合并到master
分支。
可能提issue
和实现它的人不是同一人,但是没关系~
合并到master
分支之后,这个特性就可以被标记为unstable features
了。
然后那天晚上就会被发布到nightly
渠道。
然后就是上面的火车模型啦~
附录的内容还是挺多的,虽然只是简单的说一下。
这里面比如派生宏的其它属性,都是很有用的。
如果你有操作符忘了咋用,你可以回来这个附录里看下。
下一篇就轮到我们的最终目标--web
项目实战了。
发布于 2023-01-13 12:21・IP 属地广东