Rust 学习 05
archive time: 2022-05-12
好久没更新博客了, 再学学 Rust 罢
所有权
这算是 Rust 的标志之一了, 这也是 Rust 的内存安全的保障
- 内存通过一个所有权系统来管理, 包含一组编译器在编译时检查的规则
- 当程序运行时, 所有权特性不会减慢程序的运行速度
Stack 内存
Stack 保持 LIFO (last in first out) 的规则存储数据
存数据的方式为 push ( 压栈 ), 取数据的方式为 pop ( 弹栈 )
而且在 Stack 上的数据大小是固定的或者已知的
Heap 内存
编译时大小未知的或者会改变大小的数据都会存放在 Heap 上
- 把数据放入 Heap 时会请求一定数量的空间
- 操作系统会在 Heap 中找到一块足够大的空间来存放, 并将其标记为占用, 返回该地址指针
该操作被称为 内存分配
对比
- 把值放到 Stack 上不叫做分配
- 因为指针的大小是固定的, 所以指针是可以放在 Stack 上的
- 但是要取到具体的值需要指针定位
- 数据压到 Stack 上要比在 Heap 上分配要快
- Stack 是直接在顶端存放数据, 无需寻找合适空间
- Heap 会寻找合适大小的空间来存放数据
- 访问 Stack 要比 Heap 快, 因为要找 Heap 数据需要用指针来寻找
所有权可以跟踪正在使用的数据, 而且可以最小化 Heap 上重复数据量, 并且可以清理 Heap 上未使用的数据
规则
- 每个值都有个对应变量, 该变量为该值所有者 (Owner)
- 每个值同时只能有一个所有者
- 所有者超出作用域时, 该值将会被删除 (未被使用)
作用域 (Scope) 是对象 (item, object) 有效的范围
fn main() {
// s 不可用
let s = "hello"; // s 可用
// s 作用域范围
} // s 不再可用, 对应值将被删除
String 类型
String 比基础标量类型 (scalar type 不可再分类型) 更复杂
- 字符串字面值 (literal) 是不可变的
- String 类型在 Heap 上分配, 可以存放未知数量的文本
let s1 = "hello"; // 字面值
let s2 = String::from("hello"); // String 类型
String 类型是可以修改的
fn main() {
let mut s = String::from("Hello");
s.push_str(", World");
println!("{}", s); // => Hello, World
} // drop 函数
变量和数据交互
移动
两个 5
被压入了 Stack 中
let x = 5;
let y = x;
对于 String 类型
let s1 = String::from("Hello"); // ptr, len, capacity
let s2 = s1; // s2 有和 s1 一样的值, 所以 ptr 指向同一个字符串
// s1 失效
// println!("{}", s1); // 错误 borrow of moved value
在 Java 里, 这种没有复制值的情况被称为 浅拷贝 (shallow copy)
但是 Rust 还让 s1
失效了, 故这种行为被称为 移动 (move)
clone
如果想要进行深拷贝, 则可以使用 clone
方法
let s1 = String::from("Hello"); // ptr, len, capacity
let s2 = s1.clone(); // 连 Heap 数据也复制了一份
println!("{}, {}", s1, s2); // 正常
复制
移动 和 clone 是针对于 Heap 元素说的
对于 Stack 元素执行的是复制, 深拷贝和浅拷贝是一样的
如果实现了 Copy trait, 则旧变量在赋值后仍可用
如果实现了 Drop trait, 则无法实现 Copy trait
简单的标量组合类型都是实现了 Copy 的
而需要分配内存或某种资源的都没实现 Copy 的
所有权与函数
可以通过这个例子来看看
fn main() {
let s = String::from("hello");
take_ownership(s);
// println!("{}", s); // value borrowed here after move
let x = 5;
make_copy();
println!("x: {}", x);
} // 释放 x
fn take_ownership(some_string: String) {
println!("{}", some_string);
} // drop some_string
fn make_copy(some_number: i32) {
println!("{}", some_number);
}
引用和借用
如果只想使用其值而不获取其所有权, 可以使用引用 (ref)
fn main() {
let s = String::form("hello");
let len = length(&s); // 传入引用
println!("{}: {}", s, len); // Ok
}
fn length(s: &String) -> usize { // 接收引用
return s.len();
}
把引用作为函数参数的行为被叫做 借用
在特定作用域内对某块数据只能有 一个 可变引用
- 可以防止数据竞争
- 两个或多个指针同时访问同一个数据
- 至少有一个指针用于写入数据
- 没有使用任何机制来同步 (sync) 数据的访问
但是可以创建新的作用域来允许非同时的创建多个可变引用
let mut s = String::from("hello");
{
let s1 = &mut s;
}
let s2 = &mut s;
不可以同时有一个可变引用和不可变引用, 但是可以有多个不可变引用
悬空引用 会在编译期被查出
切片
切片指向原来值的一部分
fn main() {
let mut s = String::from("hello world");
let wordIndex = first_word(&s[..]);
// s.clear(); // 不可以把 s 借用为可变
println!("{}", wordIndex);
}
fn first_word(s: &str) -> &str { // 返回字符串切片类型
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[.. i];
}
}
return &s[..];
}
好, 暂时就这样罢, 固体物理学好难啊