什么是所有权?
Rust的核心特性就是所有权
大家知道java保证内存安全是通过垃圾收集机制,不断的扫描不在使用的内存来进行内存释放从而保证内存安全。而C语言是通过手动分配和释放内存来保证内存安全
但是Rust的所有权能让Rust无需GC就能保证内存安全
栈内存(stack)和堆内存(heap)
1、stack的存储顺序先入后出(FILO);添加数据叫入栈,移除数据叫出栈;
2、所有存在stack上的数据,必须拥有已知的固定大小,编译时未知的数据或者是运行时会发生改变的数据会存放在heap上。
3、当数据存放在heap上时,操作系统会分配一块足够大的空间,并把此内存块标记为已用,并返回一个指针,指针指向此内存地址。
4、因为指针大小是固定的,所以可以把指针放在stack中,但是想要实际的数据,则需要通过指针去定位实际数据
5、将数据存放在stack上要比存放在heap快得多,因为stack不需要操作系统分配内存,永远都是把最新数据放在最顶端,在heap上分配也是需要不少时间的
6、访问heap中的数据要比访问stack中的数据慢得多,因为需要指针去定位heap中的数据(指针在内存中跳转次数越少,查找越快)
7、当你调用函数时,值被传入到函数中(也包括指向heap指针),函数内部的变量会入栈,当函数执行完毕,函数内部变量会出栈
8、所有权能跟踪那些代码正在使用存在heap上的数据,最小化heap上重复数据量,清理heap上未使用的数据
规则、内存、分配
规则
1、每个值都有一个变量,该变量是该值的所有者
2、每个值同事只能有一个所有者
3、当所有者超出作用域时,该值会被删除
变量作用域
1、Scope是程序中一个项目的有效范围
例子:
fn main() {
//xxx不可用
let xxx = "hello,world";//xxx可用
//对xxx一系列操作
}//作用域结束,xxx不可用
String类型
前面提到过4种基础数据类型(整数,小数,浮点,布尔),都是存放在stack上,而String类型是存放在heap上
字符串字面值是不可变的, String的值是可变的(因为他们处理内存的方式不同)
fn main() {
let xxx = "hello,world";
let yyy = "123";
let z = format!("{},{}",xxx,yyy);
println!("新值:{}",z);
let mut str = String::from("你好,我是赛利亚");
str.push_str("今天又是充满希望的一天");
println!("结果:{}",str);
}
输出:
新值:hello,world,123
结果:你好,我是赛利亚今天又是充满希望的一天
内存与分配
字符串字面值在编译时就知道其内容,其内容被硬编码到可执行文件中,所以它是不可变的
String为了支持其可变性,在编译时是未知的,操作系统必须在运行时来请求内存,
- 通过String::from 来实现
- 使用完String后,通过某种方式把内存返回给操作系统
- 对于有GC的语言来说,GC会清理不在使用的内存
- 对于无GC的语言来说,就需要我们自己去识别哪些内存还在使用,哪些内存不在使用
我们Rust就采用当变量走出作用域时,就立刻调用drop函数把内存返回给操作系统,属于主动行为。
变量与数据交互方式
移动(Move)
多个变量可以与同一个数据用一种特殊的方式进行交互
基础数据类型例子:
fn main() {
let x = 5;
let y = x;
println!("y: {}", y);
}
输出:
y: 5
此处y=x的副本,所以最终会有两个5会入栈
String类型例子:
fn main() {
let x = String::from("你好");
let y = x;
println!("y: {}", y);
}
输出:
y: 你好
String类型与基础数据类型不同,x的内容存在heap中,x的(指针、长度、容量)存在stack中(我们称为x1),当我们把x赋值给y时,其实是复制了一份x1为y1,把y1赋值给了y。当最后x1、y1离开作用域时,会释放两次相同的内存,会引起一个二次释放内存的bug。所以Rust在把x1复制成y1后,此时x1已经被释放,是不能再使用的。从而解决了二次释放内存的bug
例子:
fn main() {
let x = String::from("你好");
let y = x;
println!("x: {}", x);
println!("y: {}", y);
}
编译时错误:
|
2 | let x = String::from("你好");
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
3 | let y = x;
| - value moved here
4 | println!("x: {}", x);
| ^ value borrowed here after move
我们把x1赋值给y1可以称为浅拷贝,由于x1已经失效了,我们称这种操作为移动(Move),Rust不会自动创建数据的深拷贝。
深拷贝和浅拷贝
stack上的数据复制称为浅拷贝
heap伤的数据复制称为深拷贝
克隆(clone)
如果想使用到heap的深拷贝,那么我们可以使用clone。
例子:
fn main() {
let x = String::from("你好");
let y = x.clone();
println!("x: {}", x);
println!("y: {}", y);
}
输出:
x: 你好
y: 你好
x的(指针、长度、容量)存在stack中(我们称为x1),x的内容存在heap中(我们称为x2),当我们把x.clone()赋值给y时,其实是复制了一份x1为y1,x2为y2,把y1和y2赋值给了y。也就是把stack伤的数据复制了一份和heap上的数据都克隆了一份
复制(copy)
针对在stack上的数据,我们不需要克隆,我们称为复制
fn main() {
let x = 5;
let y = x;
println!("x: {}", x);
println!("y: {}", y);
}
输出:
x: 5
y: 5
在rust中基础数据类型默认实现了copy trait,所以旧变量在赋值之后仍然是可用的
任何简单标量数据类型都是实现了copy trait,任何需要分配内存的都没有实现copy trait
以下这些类型实现了copy trait
1、所有整数类型- u32
2、bool
3、char
4、浮点类型 - f64
5、tuple元组,如果所有元素都实现了copy trait那么该元组就能实现copy trait,反之则不能
所有权与函数
将值传递给函数和传递给变量是类似的
将值传递给函数讲发生移动或复制
fn main() {
let s = String::from("你好呀,我是赛利亚");//s有效
sai_li_ya(s);//s离开作用域
println!("s de result :{}",s);//s无效 因为s是String类型,调用sai_li_ya函数后,已经发生了移动
let x = 10;//x有效
base_fn(x);//x有效
println!("x de result :{}",x);//x有效,x是整数类型,默认实现了copy trait,所有调用base_fn时是传递的x的副本
}
fn sai_li_ya(s:String) {
println!("dnf: {}", s)//s有效
}//s离开作用域
fn base_fn(x : i32){
println!("base: {}", x)
}
编译错误:
2 | let s = String::from("你好呀,我是赛利亚");//s有效
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3 | sai_li_ya(s);//s离开作用域
| - value moved here
4 | println!("s de result :{}",s);//s有效
| ^ value borrowed here after move
返回值与作用域
一个函数在返回值的过程中也会发生所有权的转移
一个变量的所有权总是遵循同样的模式
把一个值复制给另一个变量时就会发生移动
当一个在heap上的变量离开作用域时,会被drop函数清理掉,除非数据的所有权转移到另一个变量上
fn main() {
let s1 = sai_li_ya();//此时s1拥有sai_li_ya返回的s的所有权
let s2 = String::from("勇士");//s2被创建
let s3 = s_send(s2);//s2被函数传递,所有权被传递到s_send函数中,s2作用域结束//s3拥有s_str返回的所有权
}//s1,s3作用域结束
fn sai_li_ya() -> String{
let s = String::from("你好呀,我是赛利亚"); //s创建
s //s所有权被返回,s作用域结束
}//s离开作用域
fn s_send(s_str : String)->String{//s_str拥有s2的所有权,
s_str//s_str所有权又被返回,作用域结束
}
如何让函数获取起值,但不获取其所有权呢?
引用(reference)与借用
以引用作为函数参数的行为,我们称为借用
&:只传递值,不传递所有权
* :解引用
fn main() {
let s2 = String::from("勇士");//s2被创建
let s3 = s_send(&s2);//s2被函数传递,所有权没有被传递,只传递了其值
}//s2,s3作用域结束
fn s_send(s_str : &str)->usize{//s_str不拥有s2的所有权,只拥有s2的值,
s_str.len()//返回s2的长度
}
引用默认也是不可变的,但是可以用通过mut变成可变的。
fn main() {
let mut s2 = String::from("勇士");//s2被创建
let s3 = s_send(&mut s2);//s2被函数传递,所有权没有被传递,只传递了其值
}//s2,s3作用域结束
fn s_send(s_str : &mut String)->usize{//s_str不拥有s2的所有权,只拥有s2的值,
s_str.push_str(", 好久不见");
println!("push result: {}",s_str);
s_str.len()//返回s2的长度
}
输出:
push result: 勇士, 好久不见
有一个限制:在特定的作用域中,一块数据,只能同时有一个可变的引用,防止在编译时发生数据竞争
fn main() {
let mut s2 = String::from("勇士");//s2被创建
let s3 = &mut s2;
let s4 = &mut s2;
println!("result : {},{}",s3,s4);
}
编译报错:
4 | let s3 = &mut s2;
| ------- first mutable borrow occurs here
5 | let s4 = &mut s2;
| ^^^^^^^ second mutable borrow occurs here
6 | println!("result : {},{}",s3,s4);
| -- first borrow later used here
在不同的作用域中可以有多个可变引用
fn main() {
let mut s2 = String::from("勇士");//s2被创建
{
let s3 = &mut s2;
println!("result : {}",s3);
}
let s4 = &mut s2;
println!("result : {}",s4);
}
输出:
result : 勇士
result : 勇士
另一个限制,不可同时拥有可变引用和不可变引用
fn main() {
let mut s2 = String::from("勇士");//s2被创建
let s3 = &s2;
let s4 = &mut s2;
println!("result : {},{}",s3,s4);
}
编译报错:
4 | let s3 = &s2;
| --- immutable borrow occurs here
5 | let s4 = &mut s2;
| ^^^^^^^ mutable borrow occurs here
6 | println!("result : {},{}",s3,s4);
| -- immutable borrow later used here
悬空引用(Dangling References)
一个指针指向了heap上一个地址,但是这个指针的其他人正在使用,但是此内存地址已经被释放了。就会引起悬空引用。
fn main() {
let mut s3 =back();//s3获取到str的引用,但是此时str已经离开了作用域,已经被释放掉
println!("result : {}",s3);
}
fn back()-> &str{//返回一个引用
let str = String::from("勇士");
&str//返回str的引用
}//str离开作用域,所有权已失效
编译报错:
error[E0106]: missing lifetime specifier
切片(slice)
切片是Rust另一种不持有所有权的数据类型
切片规则可以理解为左闭右开,形式:[开始索引..结束索引]
fn main() {
let str = String::from("hello world");
let hello = &str[0..5];
let hello2 = &str[..5];//hello等同于hello2
let world = &str[6..11];
let world2 = &str[6..str.len()];//world等同于world2
let world3 = &str[6..];//world等同于world2 等同于world3
let all = &str[..];
let all2 = &str[0..str.len()];//all等同于all2
println!("hello : {}",hello);
println!("hello2 : {}",hello2);
println!("world : {}",world);
println!("world2 : {}",world2);
println!("world3 : {}",world3);
println!("all : {}",all);
println!("all2 : {}",all2);
}
输出:
hello : hello
hello2 : hello
world : world
world2 : world
world3 : world
all : hello world
all2 : hello world