4 认识所有权
所有权(系统)是Rust最与众不同的特性,它让Rust无需垃圾回收即可保障内存安全
4.1 什么是所有权
所有运行的程序都必须管理其使用计算机内存的方式,一些语言中具有垃圾回收机制,在程序运行时不断的寻找不再使用的内存;也有一些语言,程序原必须亲自分配和释放内存。而Rust采用的是第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列规则进行筛查(有点像垃圾回收机制,但是规则完全不同),并且所有权系统的任何功能都不会减慢程序
栈和堆
栈存取数据的规则是后进先出,栈中所有数据必须占用已知且固定的大小。在编译时,大小未知或者大小可能变换的数据,要改存到堆上。堆是缺乏组织的:当向堆放入数据时,需要请求一定大小的空间。内存分配器会在堆的某处找到一块足够大的空位,并标记已使用,并返回一个改地址的指针。因此入栈比在堆上分配内存更快,访问堆上的数据必须通过指针。而所有权的存在就是为了管理堆数据,主要是跟踪哪部分代码正在使用堆上哪部分数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间
所有权规则
1.Rust中的每个值都有一个被称为其所有者的变量
2.值在任一时刻有且只有一个所有者
3.当所有者(变量)离开作用域,这个值就会被丢弃
变量作用域
作用域是一个项在程序中的有效范围,如:
fn main() {
//s 在这里无效,它尚未声明
let s = "hello"; //从此处起,s是有效的
println!("{}",s)
//此作用域已经结束,s不再有效
}
String类型
为了演示所有权规则我们得是用一个复杂的数据类型:String类型,在上面代码中,我们将字符串字面值直接赋值给变量,虽然简单好用,但是并不适合所有实际场景,比如我们要获得用户输入时。这里我们引出Rust第二个字符串类型:String,它管理的是分配到堆上的数据,所以能够存储在编译时大小未知的文本,示例如下:
let mut s = String::from("hello");
s.push_str(", world!"); //在字符串后面增加字面值
println!("{}",s); //hello, world!
但是使用字符串字面值就不能追加(不可变),区别在于两个类型对于内存的处理上
内存与分配
对于字符串字面值来说,我们在编译时就知道其内容,所以文本被直接硬编码进最终可执行文件中,这使得字符串字面值快速且高效
对于String类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这意味着:
必须在运行时向内存分配器请求内存
需要一个当我们处理完String时将内存返回给分配器的方法
第一部分由我们完成,当调用String::from时,它的实现请求其所需的内存
第二部分,在Rust中,内存在拥有它的变量离开作用域后就被自动释放(Rust会在}结尾处自动调用drop)
{
let mut s = String::from("hello");//从此处起,s是有效的
s.push_str(", world!"); //在字符串后面增加字面值
println!("{}",s); //hello, world!
}
//此作用域已经结束,s不再有效
变量与数据交互的方式(一):移动
使用字面值给变量赋值,并且把该变量的值传递给另一个变量实际上是形成了两个有效变量,并且它们都被存放到了栈上
let x = 5;
let y = x;
println!("{},{}",x,y)//5,5
使用String类型
let s1 = String::from("hello");
let s2 = s1;
println!("{}",s2)//hello
因为用String声明的字符串变量会变成两块数据吗,分别是存储在栈上和堆上,存储在栈上的是指向存放字符串内容内存的指针,一个长度和一个容量。字符串的索引和单个字符存放在堆上。当把s1赋值给s2时,实际上是复制了栈上的内容,并且s1在栈上的内容被丢弃了。并且当s2离开其作用域时,它就会释放自己的内存
变量与数据交互的方式(二):克隆
如果我们确实需要深度赋值String中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做clone的通用函数
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{},{}",s2,s1);//hello,hello
当出现clone调用时,你知道一些特定的代码被执行而且这些代码可能相当消耗资源
只有在栈上的数据:拷贝
我们前面提到了使用字符串面值赋值并且传递不会出现第一个值被丢弃的情况,事实上,只要是存储在栈上的数据都有这种操作特性。
Rust有一个叫做Copy trait的特殊注解,可以用在类似整型这样的存储在栈上的类型。如果一个类型实现了Copy trait的类型使用Copy trait ,那么一个旧的变量在将其赋值给其他变量后仍然可用。Rust 不允许自身或其任何部分实现了Drop trait 的类型使用Copy trait。
实现了Copy trait 的类型:所有整型、布尔类型、所有浮点类型、字符类型、元组(当且仅当其包含的类型也都实现Copy的时候)
let x = 5;
let y = x;
println!("{},{}",x,y)//5,5
所有权与函数
将值传递给函数在语义上与给变量赋值相似,向函数传递值可能回移动或者复制,如下:
fn main() {
let s = String::from("hello");
takes_ownership(s);// s的值移动到函数里...,但不是string不是copy trait类型,因此无法再次被调用
let x = 5;
makes_copy(x);//x移入了函数里,但是i32是copy的,所以在后面可以继续调用
println!("{}",x)//5
}
fn takes_ownership(some_string:String){
println!("{}",some_string)
}
fn makes_copy(some_integer:i32){
println!("{}",some_integer)
}
返回值与作用域
返回值也可以转移所有权
fn main() {
let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
}//s1被移出作用域被丢弃,s2被移给s3,s3被移出作用域并被丢弃
fn gives_ownership()->String{
let some_string = String::from("hello");
some_string
}
fn takes_and_gives_back(a_string:String)-> String{
a_string
}
如果我们仅想获得变量的所有值而不需要获得所有权呢?可以使用元组或者引用
fn main(){
let s1 = String::from("hello");
let (s2,len) = calculate_length(s1);//多个变量要使用括号
println!("The length of '{}' is '{}'",s2,len);
}
fn calculate_length(s: String)-> (String,usize){
let length = s.len();
(s,length)
}