四、作用域与值传递
1. 作用域
-
在 Rust 中,常用的有两种作用域:块级作用域、模块作用域
- 这两种作用域类似于
ECMAScript 6中的块级作用域和模块作用域- 不同于 Javascript 的是,Rust 没有函数作用域
- 这两种作用域类似于
-
在 Rust 中,通常来说,一个文件即为一个模块,可以为这个模块指定 私有/公有 元素 (结构体/枚举/函数/值)
-
在 Rust 中,变量定义之前是不可访问的 (常量可以),变量被重新定义之后,旧变量是不可访问的
// main.rs const OUTER_2: i32 = OUTER + 1; // 这个定义是允许的 const OUTER:i32 = 123; // OUTER 可以在这个模块内的任意位置被访问 fn main() { // 这里以上、main 函数外的任意位置都不能访问 a let a = "abc"; println!("a = {}, OUTER = {}", a, OUTER); { // 这里及以上,都不能访问 b let b = "innerB"; println!("a = {}, b = {}, OUTER = {}", a, OUTER); // 这个块以外的任何地方都不能访问 b } // 这里以上,一直到 a 初次被定义的位置,都只能访问到初次定义的变量 a let a = 456; // 这里以下,一直到 main 函数结束的位置,都只能访问到重新定义的变量 a println!("a = {}, OUTER = {}", a, OUTER); } -
学习这部分最简单的方式,就是尝试在 IDE 中直接去定义变量,然后观察 IDE 给出的提示
-
如果期望某个模块定义的
函数/常量/类等可以被其他模块访问,可以使用关键字pub暴露出去 -
Rust 中与作用域相关的还有另一个概念:生命周期
- 这个生命周期是指变量的可用状态,和其他语言、框架中的生命周期的概念不同,尤其是和
Vue,React等前端框架的生命周期概念完全不同
- 这个生命周期是指变量的可用状态,和其他语言、框架中的生命周期的概念不同,尤其是和
2. 所有权
- 每一个值,都有且只有唯一一个拥有它的变量,这个变量被称为值的所有者 (owner)
- 当所有者所在的作用域被销毁时,所有者对应的值也会被丢弃
2.1 租借值 (引用)
-
这个概念和 C 语言中的指针很类似,相关的一些概念和操作也是类似的
-
通过 租借/取引用 某个变量而得到的值
-
特性:允许使用这个值,但不允许改变这个值
-
所有者:被
&租借的原始对象 -
存在堆里的数据
- 可被租借的值,这些值的原始值都是存在堆里的,栈里只保存了引用
- 不可通过引用来修改实际所有者的值
- 如果一个所有者的值被借用了,那么这个所有者的值将不允许被修改,直到租借值被释放
-
引用的规则
- 引用必须总是有效的
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用
- 不能同时有既有可变引用又有不可变引用
这部分应该要画一个图才能更好地理解,但是我懒,所以没有画
// 一个示例。建议直接将这部分代码 copy 到 IDE 中查看
fn main() {
let hello = String::from("Hello, world!");
let quoting = &hello;
quoting.push_str(" 123"); // 报错:cannot borrow `*quoting` as mutable, as it is behind a `&` reference (不能借用 `*quoting` 作为可变的变量,因为它在 `&` 引用后面)
let mut hello2 = String::from("Hello, world!");
let quoting2 = &hello2;
quoting2.push_str(" 123"); // 和上面的报错一样,而且 hello2 还会给 warning 提示 `没必要定义为可变的`
let mut hello3 = String::from("Hello, world!");
let quoting3 = &mut hello3;
quoting3.push_str(" 123"); // 可以正确执行
println!("{}", quoting); // Hello, world! 123
let quoting4 = &hello; // 允许,因为 hello 是不可变引用
}
2.2 变量的值传递
-
在 JavaScript 中
-
JavaScript 中有两种值类型:
- 原始值
- 引用值
-
这两种值类型发生拷贝时:
- 原始值直接拷贝,两个变量之间的修改互不影响
- 引用值只拷贝引用,当通过一个引用来修改某属性时,另一个引用对应的属性也会发生改变
// JavaScript 示例代码 let name = "Cherry"; let secondName = name; name = "Jelly"; console.log("name =",name); // name = "Jelly" console.log("secondName =", secondName); // secondName ="Cherry" const user = { name: "Cherry", }; const secondUser = user; user.name = "Jelly"; console.log("user.name =", user.name); // user.name = "Jelly"; console.log("secondUser.name =", secondUser.name); // secondUser.name = "Jelly"; console.log(user ==== secondUser); // true; console.log(user.name ==== secondUser.name); // true;
-
-
在 Rust 中,Rust 有一套特有的传递规则
-
简单拷贝
- 规则描述:直接将原变量的值拷贝一份给新变量,两个变量互不影响
- 类似于 JavaScript 中的原始值拷贝
- 这个规则仅针对保存在栈中的数据,即这些值的类似是基本数据类型
- 整型
- 浮点型
- 布尔型
- 字符型
- 以及 仅包含以上数据类型的元组 (数组都不行,只能是元组)
- 特殊情况:引用的传递规则也是简单拷贝
-
直接移动
-
规则描述:直接将原变量持有的引用传递给新变量,原变量不可继续使用,仅能使用新变量
-
这个规则针对的是保存在堆中的数据,即这些值的类型不是上一条规则描述到的基本数据类型
let hello = String::from("Hello, world!"); let world = hello; println!("{}, {}", hello, world); // × borrow of moved value: `hello`。即 `hello` 变量已失效 // 特例 let s1 = "hello world"; let s2 = s1; println!("{}, {}", hello, world); // √ s1 和 s2 拿到的都是字符串的引用,是 &str 类型
-
-
堆数据克隆
-
“ Rust 会尽可能地降低程序的运行成本,所以默认情况下,长度较大的数据存放在堆中”
-
如果期望将数据备份一份以供其他地方使用,就需要采用 克隆 的方式
-
比如字符串:
fn main() { let hello = String::from("Hello, world!"); let world = hello.clone(); // 把 hello 进行一次克隆 println!("{}, {}", hello, world); // hello 和 world 指向了堆里的两个独立的、一模一样的字符串对象 }
-
-
-
变量在作为函数参数时的传递规则
-
基本数据类型
- 传递规则与上文描述的
简单拷贝规则一致
- 传递规则与上文描述的
-
非基本数据类型
- 传递规则与上文描述的
直接移动规则一致
- 传递规则与上文描述的
-
租借值/引用
- 传递规则与上文描述的
简单拷贝规则一致
- 传递规则与上文描述的
-
3. 切片 (Slice)
-
切片,即对引用类型的数据值的部分引用
- 常用的是数组切片、字符串切片
-
切片的特性
- 字符串字面值就是 slice
- 数组也可以利用类似的方式,来获取数组的切片
- 我的理解:字符串是一种特殊的数组 (字符数组),字符串切片是一种特殊的数组切片 (类型为 &u8 的切片)
-
示例代码
// 来自官方教程的一个示例 pub fn word_test() { let mut hello = String::from("hello world!"); let hello_first = first_word(&hello); println!("first word: {}.", hello_first); hello.clear(); } fn first_word(source: &str) -> &str { let charts = source.as_bytes(); let iter_wrapper = charts.iter().enumerate(); for (index, &item) in iter_wrapper { if item == b' ' { return &source[0..index]; } } return &source; }