周末无事看看tidb开发者说很好用的rust,觉得很有趣,原来现代编程语言就是这种感觉,有一些细节上的简化。
文档参见: https://rustlang-cn.org/office/rust/book/getting-started/ch01-03-hello-cargo.html
源码:
1 | fn main() { |
编译和运行:
1 | rustc main.rs |
用cargo
(类似于maven
)
1 | cargo new hello_cargo # 创建项目 |
可以看到专门提供了一个cargo check
命令来避免编译、只是检查语法,看来网上大家说rust编译慢很可能是真的。
变量
1 | let foo = 5; // 不可变 |
类方法/静态函数: 在rust中叫关联函数
(associated function);
crate: 库
类似于mvn的中央仓库: https://crates.io/crate
不是创建的意思,差了一个字母,是rust的库的意思。
数据类型
i32
: 32位数字;u32
: 32位无符号数字;i64
: 64位数字等等。
Rust
默认使用i32
.
但是如果你用u32
类型和一个变量a
比较,Rust会推断出a也是u32
类型.
let关键字
let
类似于js里的let
,用来定义一个变量,而且支持shadowing
。
比如一开始定义了一个string类型的a;
后来转换成数字以后,可以直接
1 | let a:u32 = a.guess.trim().parse() |
新的定义会覆盖以前的,这样就不用定义两个变量了(一个string_a,一个u32_a)。
原理上其实底层是生成了两个变量,因此可以把let mut
覆盖成let
,或者把不可变的覆盖成可变的。实测了一下确实也是可以的。
match关键字
match和scala里的一样
1 | match xxx { |
通用概念(与其他编程语言核心对应的)
关键字转义
比如match
在rust中是一个关键字,所以如果恰好有一个函数叫这个名字,需要转义以后才能调用:(用r#
前缀)
1 | r#match(); // 调用名为 'match' 的函数 |
不可变
1 | const MAX_POINTS: u32 = 100_000; // 不可变,直接赋值; |
数据类型
rust会尝试推断数据类型,推断不出来则会报错;
标量: 整型、浮点型、布尔类型和字符类型;
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
arch | isize | usize |
这里的arch
: 64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。isize
和usize
主要作为索引类型。
赋值的时候: (还能在中间随意加横杠_
):
数字字面值 | 例子 |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) | b’A’ |
57u8
表示57,数据类型是u8
;
数字溢出: debug版检查溢出并报错;
release版会进行溢出。
可以用Wrapping
类型来使用溢出特性,以免被debug版本报错。
f32
: 32位浮点数;f64
: 64位浮点数. (默认类型,现代cpu下性能与f32几乎一样)bool
: 布尔值。char
: Unicode字符。
元组和数组
元组下标从0开始(和scala不同,scala从1开始)
1 | let tup: (i32, f64, u8) = (500, 6.4, 1); |
数组:
1 | let a = [1, 2, 3, 4, 5]; |
函数
用fn声明.
1 | fn main() { |
表达式
表达式的结尾没有分号
1 | let y = { |
循环
loop,while,for
1 | let result = loop { |
ownership 所有权
rust无需gc。
要学习的点包括: 借用、slice、内存布局。
所有权:管理堆数据
所有权的三大法则:
- Rust中的每一个值都有一个被称为其 所有者(owner)的变量。
- 值有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
创建一个堆上的变量:
1 | { |
s的大小运行时可变,因此它显然分配在堆上。(栈每个slot大小相同)
浅拷贝、深拷贝、移动
rust对复杂类型默认是移动;
基本类型直接深拷贝。
rust没有浅拷贝、只有移动。
1 | // 移动: |
总结就是:
浅拷贝: 无;
深拷贝: 显式调用clone
、或者是基本类型;
移动: 复杂类型;
如果一个类型拥有Copy trait
,
一个旧的变量在将其赋值给其他变量后仍然可用。
rust的逻辑是,如果发现一个类型没有实现Copy
,它就进行move
。
除了用等号,调用函数时也会发生移动或者深拷贝。
例如:
1 | let s = String::from("hello"); // s 进入作用域 |
函数return的时候也类似于等号,也会发生移动或者深拷贝,因此可以用return再取回所有权。
1 | let s = String::from("hello"); |
可以用&
号来简化这个过程:
1 | let s1 = String::from("hello"); |
这里calculate_length
函数没有所有权,因此是借用了s1变量。
借用
函数借用变量s,不拥有所有权。
1 | fn main() { |
借用并且要修改的话,要显式写上&mut
.
借用的竞态
rust默认禁止竞态,编译不予通过:
1 | let mut s = String::from("hello"); |
借用结束的话可以消除竞态:
1 | let mut a = String::from("hello world"); |
悬挂指针
Rust 中编译器确保永远不会有悬挂指针。
构造悬挂指针:
1 | fn main() { |
slice
slice的类型多一个&,属于不可变引用。
比如string的slice类型: &str
,
字符串的字面量的类型:str
slice语法很简单:
1 | let s = String::from("hello"); |
用slice的好处是可以预防错误,因为持有了不可变引用,其他试图修改s的操作就会被阻止,因为修改s的时候会申请可变引用,根据上一节中的竞态阻止,申请可变引用会失败。
数组的slice
类型是 &[i32]
1 | let a = [1, 2, 3, 4, 5]; |
结构体struct
1 | # struct User { |
以前写代码经常会有this.email=email
这种机械重复的代码,rust提供了简写省略的方法。
构造函数的简写: (new
)
1 | fn build_user(email: String, username: String) -> User { |
类似的,结构体的update也有相应的简写:
1 | let user2 = User { |
结构体的实例方法和类方法区别在于有没有第一个&self
参数,方法可以位于不同impl
块中:
1 | # |
自动解引用功能
统一obj.xxx()
操作和obj->xxx()
.
rust自动解引用:
当使用 object.something() 调用方法时,Rust 会自动为 object 添加 &、&mut 或 * 以便使 object 与方法签名匹配。
枚举
枚举可以直接绑定数据类型:(类似于一种typedef
)
1 | enum IpAddr { |
也可以作为朴素的数据(数字):
1 | # enum IpAddrKind { |
Option
也是一种枚举类型。
match
match的时候可以自动unapply枚举型:
1 | match x { |
这里类似于最后default兜底的值是_
,返回值是()
也就是unit
类型。if let
是match的语法糖:
1 | let mut count = 0; |
包
package: 包。cargo的功能.cargo new
生成,带有Cargo.toml
文件;里面可以有多个库。
Crates: 库。很多模块构成的库;(或者程序)
Modules: 模块。
包默认生成的库:
src/main.rs
; (程序、数量任意)src/lib.rs
; (库、最多1个)
模块
1 | mod sound {// 同包下可以访问 |
super相对路径
1 | mod sound { |
use关键字引用
类似于import
1 | use crate::sound::instrument; |
默认use引入的项变成了私有,可以再加上pub让引入的项维持公有:
1 | pub use crate::sound::instrument; |
访问修饰符pub
枚举enum
: 一旦pub,则所有字段pub;
结构体struct
: 必须显式设定每个字段为pub,默认是private;
模块化
每个mod放在自己的同名文件中,其他文件中要用的时候,声明一下即可:
1 | mod sound; |
末尾是分号。