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

可变引用

  1. 将变量本身改为可变的
  2. 将函数的参数改为可变引用
  3. 传入函数时传入可变引用
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)“

当以下三条满足时,会发生竞态

  1. 两个或以上指针在同一时刻访问同一份数据
  2. 至少有一个指针在写数据
  3. 没有机制被用来支持同步写数据

限制 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 提供了类似于其他编程语言对于管理内存使用的的方式,但数据的拥有者会在其离开作用域时自动清空数据。