rust入坑之旅-Ownership

Scroll Down

所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过Ownership系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。

Ownership

请谨记这些规则:

  • Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
  • 值在任一时刻有且只有一个所有者。
  • 当(owner)所有者(变量)离开作用域,这个值将被丢弃。当变量离开作用域后,Rust 自动调用 drop函数并清理变量的堆内存。

栈和堆

栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。不能从中间也不能从底部增加或拿走盘子!增加数据叫做 入栈,而移出数据叫做 出栈。

栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 “分配”(allocating)。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的,你可以将指针存储在栈上,不过当需要实际数据时,必须访问指针。

想象一下去宾馆开房。当服务员给你房卡时,你能去楼上2203房间,你朋友来迟了要找你,他们也可以通过服务员你的房号来找到你在哪里。

字符串String

扫盲贴的类型都是已知大小的,可以存储在栈中,并且当离开作用域时被移出栈,如果代码的另一部分需要在不同的作用域中使用相同的值,可以快速简单地复制它们来创建一个新的独立实例。使用 String 作为例子可以更好地理解Rust如何在堆上存储数据

fn main() {
let s = String::from("hello");
}

String 由三部分组成,如图左侧所示:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分,这里顺道画下栈上变量的复制。
image.png

两个冒号(::)是运算符,用于调用 String 类型的命名空间(namespace)下的from 函数。
对字符串进行修改

fn main() {
let mut s = String::from("hello");

s.push_str(", world!"); // push_str() 在字符串后追加字面值

println!("{}", s); // 将打印 `hello, world!`
    println!("test_copy_stack is  {}", test_copy_stack(5));
    println!(
        "test_copy_stack is  {}",
        test_copy_heap("222".parse().unwrap())
    );
}
fn test_copy_stack(z: i32) -> i32 {
    println!("let Z is {}", z);
    let x = 6;
    let y = x;
    let d = z + 1;
    println!("let X is {}", x);
    println!("let y is {}", y);
    d
}
fn test_move_heap(z: String) -> String {
    println!("let Z is {}", z);
    let y = z;
    // 如果你在其他语言中听说过术语 浅拷贝(shallow copy)和 深拷贝(deep copy),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是浅拷贝。
    // println!("let Z is {}", z);
    println!("let y is {}", y);
    y
}

字符串move let y = z;
image.png
如果真的需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的通用函数,在作用域拥有2份堆上数据 除了作用域 俩个变量都会释放堆上的内存

fn test_clone_heap(z: String) -> String {
    println!("let Z is {}", z);
    let y = z.clone();
    println!("let Z is {}", z);
    println!("let y is {}", y);
    y
}

Clone
image.png

ownership && Func

将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。返回值用于接收函数的返回所有权。

fn main() {
    let s = String::from("hello"); // s 进入作用域

    takes_ownership(s); // s 的值移动到函数里 ...

    //println!("x is {}", s); 这里会报错 因为s以及呗移动了
	//  		  ^ value borrowed here after move
    let x = 5; // x 进入作用域

    makes_copy(x);
    println!("x is {}", x);
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
}