在理解堆栈的前提下, 更有利理解Rust的所有权
1.Rust中的每一个值 有且只有一个所有者(变量) let s = String::from("teststr") // 变量s就是字符串teststr的所有者 2.当所有者(变量)离开作用域范围时,这个值将被丢弃(free) 也就是释放内存空间 fn test() { let s = String::from("teststr") // s为test函数中的局部变量 } // 函数执行完成 变量s 离开作用域 字符串teststr的内存将被释放 生命周期结束 简单介绍String类型上边提到了String::from 方法 , 创建变量的类型是String
let s = String::from("teststr") // 变量s就是字符串teststr的所有者还有一种声明字符串的例子 这种声明的字符串类型是 字符串字面值 a 是被硬编码到程序的类型是&str 他不可修改
let a = "test" 所有权背后的数据交互下面看这样一段代码
let x = 5; // x 变量就是 整数5的所有者 let y = x; // 拷贝 x 赋值给 y 最终x和y都等于5 且都可以调用 因为上述操作都是在栈中运作的 整数类型是rust的基本类型 基本类型赋值调用都会自动拷贝 不会在堆中进行分配使用 也不会引发所有权机制 // 可能有好奇宝宝 会想 这种栈中的的copy赋值 是不是太慢了些, 但是实际上在rust的基本类型足够简单 ,拷贝会非常快, 只需要赋值一个i32,4字节的内存即可随即看这样一段代码:
let s1 = String::from("hello"); let s2 = s1; println!("{}{}", s1, s2) // 跟上边的整型拷贝很像吧 但是 String类型 并不是rust的基本类型 所以他是存放在堆上的 不会自动拷贝 此时打印s1,s2就会触发rust的所有权机制 // 我们可以先看一下上边这段代码具体发生了什么 //String类型是一个复杂的类型, 他的堆指针、字符串长度、字符串容量共同存放在栈中, 真实数据存放在堆中,下面我们分析 let s2 = s1 可能出现的两种情况 1.拷贝栈上String堆指针 容量 长度 和存储在堆上的字节数组, 这就是深拷贝了 2.只拷贝String的堆指针 容量 长度 8+8+8字节 理解为浅拷贝, 但是这样就跟Rust所有者机制产生了冲突 因为我们的数据的所有者有且只能有一个, 如果按照这种浅拷贝的情况 那么这个数据就出现了两个所有者, 那么当s1和s2离开作用域的时候都会释放同一块内存, 也称为二次释放, 导致内存污染 违背了Rust的所有权机制, 那么Rust是如何处理这种问题呢? 解决方法: 当s1将值赋值给s2的时候, Rust认为s1不再有效, 因此也无需在s1离开作用域后drop释放s1的内容, s1的数据的所有权已经转移给了s2, s1同时也就失效了, 不会产生二次释放的问题, 效率大大增加,上图中就是第二中浅拷贝的情况rust解决的方案, s1赋值给s2后 s1自动失效, s2接管这块内存地址
深拷贝Rust永远不会自动创建数据的"深拷贝", 因此, 任何的自动复制都不是深拷贝. 浅拷贝被认为运行时性能影响较小
let s1 = String::from("hehahi"); let s2 = s1.clone(); // 深拷贝 println!("{}{}", s1, s2)此段代码编译运行畅通无阻, 因为s2 完成的clone了s1 包括栈内的堆指针 容量 长度 堆内的数据, 但是如果频繁使用clone深拷贝 将会带来性能上的降低。
函数参数传递及返回 所有权的转移在变量作为参数传递给函数是, 同样会发生移动或者复制, 所有权就会对应的产生变化
fn main() { let s = String::from("hello"); // s 进入作用域 takes_ownership(s); // s 的值移动到函数里 ... // ... 所以到这里不再有效 let x = 5; // x 进入作用域 makes_copy(x); // x 应该移动函数里, // 但 i32 是 Copy 的,所以在后面可继续使用 x } // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走, // 所以不会有特殊操作 fn takes_ownership(some_string: String) { // some_string 进入作用域 println!("{}", some_string); } // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放 fn makes_copy(some_integer: i32) { // some_integer 进入作用域 println!("{}", some_integer); } // 这里,some_integer 移出作用域。不会有特殊操作我们如果尝试在takes_ownership(s); 语句执行之后 打印s值 就会产生报错 因为s作为参数传递给takes_ownership函数 String类型 不是基本类型 不会自动拷贝, 所以String的所有权转移到函数内, 又转移给了println宏当中 但函数执行完成, String开辟的这块内存已经被释放了 所以在函数之后打印s 就会报错 ,但是如果makes_copy(x) 函数之后执行打印x 就不会报错的, 因为i32类型是基本类型, 存储在栈内会进行自动拷贝, 不会触发所有权机制 , 但如果不是存储在栈的数据 就需要将数据返回出来, 这样数据传来传去 很是麻烦, Rust就帮我们解决了这个问题 引入了借用机制。
借用