rust 语法融合了多门编程语言的特性,可以看到很多语言的影子,例如 JavaScript、Java、Python、C、Go 等。
打印
println!()
是宏定义,不是普通函数
类比 C 语言以及 python2 的 print
代码格式
缩进为 4 个空格,而非tab
(因为 tab 不一定对应 4 个空格)
类比 Python 语言,严格要求缩进作为有效格式
类型
字符使用单引号,字符串使用双引号
类比 C 和 Java,JavaScript 中的单引号和双引号没有区别
函数
声明函数使用fn
类比 Go 语言中函数的声明
元组和数组
元组内可以存放不同类型的值,数组只能存放相同类型的值。
数组在声明时需要制定确定的大小,即数组元素的个数。
类比 Python 中的元组和列表
解构
对于元组或数组内的元素,都可以使用解构取出其内部的值。解构就是从一个大的容器内取出对应位置的值的操作。
类比 JavaScript 中的解构
栈和堆
栈是连续内存,堆是随机分配的内存,因此申请栈比申请堆要快,所以一般将不确定大小或未来可能变化的变量存在在堆,并将访问该变量的指针(地址)存在栈。这样可以从栈中快速找到变量指针,然后通过指针访问具体的值。
整型、浮点型和布尔类型的变量创建时都申请固定大小的内存,所以它们都存在栈中。
字符串占用的内存不确定且可能被改变,因此存放在堆内存。
类比 JavaScript 和 Java 对于变量声明的内存分配方式,但 JavaScript 的字符串因为是基本类型,所以存在栈而非堆中
变量不可变
变量默认都是不可变的,如果希望变量可被改变,需要使用mut
(意指”mutable“)进行声明。
所有权
类比其他编程语言中的"按值传参 v.s. 按引用传参"
所有权转让,对应将值传入函数,所有权会跟着进入函数内部,当函数执行完成,如果没有把所有权返回,值所在内存会被自动回收(drop),类似于”按值传参“。
将引用传入函数,所有权未被传入函数,当函数内对于变量访问结束后,虽然函数作用域结束了,但是变量的所有权因为未被转移而仍然存在,因此不会被自动回收,类似于”按引用传值“。
这种使用引用访问值的方式称为"借用",就像生活中一个物品的所有者,将物品借给你用,当你使用完后会进行归还,从始至终物品的所有权都未曾属于你,你只是在"借用"。
类比 Java
可变引用
- 将变量本身改为可变的
- 将函数的参数改为可变引用
- 传入函数时传入可变引用
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
限制 1:只能被借用一次
只能被借用一次(即不能被多个变量访问其可变引用)
这是为了避免”竞态(race condition)“
当以下三条满足时,会发生竞态
- 两个或以上指针在同一时刻访问同一份数据
- 至少有一个指针在写数据
- 没有机制被用来支持同步写数据
限制 2:当被用来作为不可变借用后不能再进行可变借用
一旦变量被作为不可变借用后,不能再作为可变借用,因为可变借用会导致前面的不可变借用被篡改。
但是多个不可变借用是可以的,因为它们都是在读数据,不会改变数据。
摇摆指针
将某个作用域内对于内存的引用传递给作用域以外,类似于 js 的闭包
fn main() {
let reference_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
类比 JavaScript 中的闭包,将函数内的变量引用暴露给函数外的作用域
这里在dangle
函数内部声明了字符串引用s
,并试图将这个引用通过函数返回。但rust
不会允许这么做,当函数执行结束,其作用域内的引用会被回收。
如果想要访问函数内生成的变量,可以使用非引用的值
fn main() {
let s = dangle();
}
fn dangle() -> String {
let s = String::from("hello");
s
}
这里s
的所有权转移到了函数外,没有引用被回收。
字符串切片
可以通过..
符对字符串进行切片
类比 Python 中的切片和 JavaScript 中的 slice 方法
字符串字面量切片
let s = "Hello, world!";
这里s
的类型是&str
,它其实是指向二进制指定位置的切片。这也是为什么字符串字面量是不可变的,并且&str
是不可变引用。
这里的&str
存储着切片开始的位置,以及切片的长度,从而可以计算出切片部分的值。
小结
Rust 拥有所有权、借用和切片概念,在编译时保障了内存使用的安全。
Rust 提供了类似于其他编程语言对于管理内存使用的的方式,但数据的拥有者会在其离开作用域时自动清空数据。