之前我们了解 java、javascript 和 cpp 这些语言对内存管理无非是由语言通过 GC 来管理内存还是由 developer 来管理内存,而今天 rust 给我们带来一种的新的内存管理方式。
rust 通过所有权机制来管理内存,编译器在编译就会根据所有权规则对内存的使用进行检查来回收内存。
堆和栈
堆和栈是两种数据结构,都是数据项按序排列的数据结构,栈是个特殊的存储 区,主要功能是暂时存放数据和地址,堆是队列优先,先进先出(FIFO—first in first out)而栈是先进后出(FILO—First-In/Last-Out)。
有关堆和栈的详细信息,请参照详解 JVM 的机制
作用域
有关作用域,作用域与变量相关,也就是变量作用的范围,通过划分一定范围,变量只在这个范围(作用域)内有效。
fn main() {
let x:i32 = 1;
let y:i32 = 2;
println!("x = {}",x);
println!("y = {}",y);
}
我们输出 x 和 y 的值,当我们用一对大括号(定义范围)来将 y 变量括起来,在大括号内 y 是有效,当 y 离开大括号限定的作用域外,就被垃圾回收了。所以在定义 y 的大括号外我们无法访问到 y 所有对应变量的。
fn main() {
let x:i32 = 1;
{
let y:i32 = 2;
println!("x = {}",x);
}
println!("y = {}",y);
}
println!("y = {}",y);
| ^ help: a local variable with a similar name exists: `x`
表示 y 有效区域(作用域)只在作用域内有效,出作用域就被回收。
fn main() {
let x:i32 = 1;
{
let y:i32 = 2;
println!("x = {}",x);
println!("y = {}",y);
}
}
对于指定确定类型的变量通常会被分配到栈内存上,例如 x 和 y 已经指定了类型 i32 所有分配的内存大小也是固定所有分配在栈上。
fn main() {
let x:i32 = 1;
{
let y:i32 = 2;
println!("x = {}",x);
println!("y = {}",y);
}
{
let str = String::from("hello");
println!("str = {}",str);
}
}
这里 str 定义在堆上,String 类型字符串类型,编译器是无法确定 Str 的大小的,例如我们可以通过 push_str
来改变字符串。
fn main() {
let x:i32 = 1;
{
let y:i32 = 2;
println!("x = {}",x);
println!("y = {}",y);
}
{
let mut str = String::from("hello");
str.push_str(" world");
println!("str = {}",str);
}
}
fn main() {
let x:i32 = 1;
{
let y:i32 = 2;
println!("x = {}",x);
println!("y = {}",y);
}
{
let str1 = String::from("hello");
//str1.push_str(" world");
println!("str1 = {}",str1);
let str2 = str1;
println!("str2 = {}",str2);
println!("str1 = {}",str1);
}
}
通过上面代码大家知道为什么 String 大小不是固定的了。所以编译器将 str 分布
- prt 指针
- len 长度
- capacity 扩展能力
在定义 str1 变量,变量 str1 保存一个指向内存地址指针,也就是看成是内存的引用。
在 cpp 语言我们通过将变量 str1 赋值给 str2,那么 str1 和 str2 保存指针指向同一个内存地址,当 str1 和 str2 离开作用域就会被回收,但是因为他们都指向同一块内存,所以这块内存会被释放两次所有发生错误。
在 rust 当将 str1 赋值给 str2 不再是 str1 和 str2 同时指向同一块内存,而是将 str1 对这块内存所有权将给了 str2。
所以在当 str1 将所有权交给了 str2 后再次打印 str1 就会报错。
println!("str1 = {}",str1);
| ^^^^ value borrowed here after move
这里所说 move 将所有权从 str1 移至 str2。
移动
这里不得不说一下 rust 报错信息很准确详细的,这也是他能够得到大家喜欢的一个原因吧。有点类似 cpp 的浅拷贝,也就是复制指针同时指向同一块内存。我们知道在 String 类型离开作用域时候会调用 drop 方法来释放内存,因为 str1 和 str2 都指向同一块内存所以当离开作用域,他们指向内存将会被释放 2 次。所以 rust 语言在复制,move 后 str1 就无效,并不不是指向同一,所以... str1 是无效,所以发生上面报错。rust 通过move 指针来解决了上面内存释放两次问题
fn main() {
let x:i32 = 1;
{
let y:i32 = 2;
println!("x = {}",x);
println!("y = {}",y);
}
{
let str1 = String::from("hello");
//str1.push_str(" world");
println!("str1 = {}",str1);
let str2 = str1;
println!("str2 = {}",str2);
// println!("str1 = {}",str1);
}
}
clone
fn main() {
let x:i32 = 1;
{
let y:i32 = 2;
println!("x = {}",x);
println!("y = {}",y);
}
{
let str1 = String::from("hello");
//str1.push_str(" world");
println!("str1 = {}",str1);
let str2 = str1;
println!("str2 = {}",str2);
// println!("str1 = {}",str1);
//clone
let str3 = str2.clone();
println!("str2 = {}",str2);
println!("str3 = {}",str3);
}
}
栈上数据拷贝
fn main() {
let a = 1;
let b = a;
println!("a = {}, b= {}",a,b);
}
在栈上变量直接拷贝,分配在栈上只有就是clone行为,叫 copy 的特征。只要类型实现 copy 特征,在赋值其他变量依然可以使用,常用具有 copy 特征类型,所有整型、浮点型、布尔型、字符类型和元组。
函数和作用域
fn take_ownership(str:String){
println!("{}",str);
}
fn make_copy(a:i32){
println!("a = {}",a)
}
fn main() {
let str1 = String::from("hello");
take_ownership(str1);
let x = 5;
make_copy(x);
}
fn main() {
let str1 = String::from("hello");
take_ownership(str1);
println!("{}",str1);
let x = 5;
make_copy(x);
}
fn main() {
let str1 = String::from("hello");
take_ownership(str1);
// println!("{}",str1);
let x = 5;
make_copy(x);
println!("x = {}",x);
}
fn take_ownership(str:String)->String{
println!("{}",str);
str
}
fn main() {
let str1 = String::from("hello");
let str2 = take_ownership(str1);
println!("{}",str2);
let x = 5;
make_copy(x);
println!("x = {}",x);
}