1.认识所有权
- 所有权(系统)是Rust最为与众不同的特性,它让Rust无需垃圾回收器,即可以保证内存安全。
2.什么是所有权?
2.1程序运行管理运行的方式
- 一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存
- 一些语言中,开发者必须亲自分配和释放内存
- Rust中,通过所有权系统管理内存,编译器在编译时会根据一系列规则进行检查;在运行时,所有权系统的任何功能都不会减慢程序
2.2栈(Stack)和堆(Heap)
- 栈和堆都是代码在运行时可供使用的内存
1.栈(Stack)
- 栈以放入值的顺序存储值并以宪法顺序取出值,也称为先进后出
- 栈中的所有数据都必须占用已知固定的大小
- 入栈比堆上分配内存要快,入栈时分配器无需为存储新数据去搜索内存空间,其位置总是在栈顶
2.堆(Heap)
- 在编译时大小未知或大小可能变化的数据,要改为存储在堆上
- 堆时缺乏组织的,当向堆放入数据时,需要请求一定大小的空间
- 内存分配器在堆的某处知道到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针
- 访问堆上的数据比访问栈上的数据慢,必须通过指针来访问
2.3所有权规则
- Rust中的每一个值都有一个被称为其所有者的变量
- 值在任意时刻有且只有一个所有者
- 当所有者离开作用域,这个值将被丢弃
2.4变量作用域
- 作用域是一个项(item)在程序中的有效范围
fn main(){ //str在这里无效,它尚未声明
let str = "生活很美好"; //从此处起,str开始有效
} //此作用域已经结束,str不再有效
2.4String类型
let str = "hello"
和let str = String::from("hello")
的区别let str = "hello"
- 这种方式创建的字符串是静态分配的,存储在程序可执行文件中,并且在运行时不可变
- 优势
- 性能高效:因为字符串时静态分配的,不需要额外的内存分配和释放操作
- 编译时检查:编译器可以在编译时检查字符串的有效性,避免一些运行时错误
let str = String::from("hello")
- 优势
- 动态大小: 可以根据需要动态增加或减少字符串的长度
- 可变性: 可以修改字符串的内容
- 动态分配: 适用于需要在运行时构建字符串的情况
- 优势
fn main(){
let str = "hello";
let mut str1 = String::from("hello");
str1.push_str(",Tom");
println!("{}",str1);// 输出 hello,Tom
}
2.5内存与分配
- 针对字符串字面量,在编译时就知道内容,文本被直接硬编码进最终的可执行文件中,快速且高效,但是字符串字面量不可变
- 对于String类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容
- 必须在运行时向内存分配器请求内存
- 需要一个当我们处理完String时将内存返回给分配器的方法
1.以String类型为参考
- 当调用String::from(" "),它的实现请求其所需的内存
- 释放内存
- 有垃圾回收机制的语言中,GC记录并清除不再使用的内存
- 没有GC的话,识别除不再使用的内存并调用代码显式释放
- Rust采用了一个不同的策略: 内存在拥有它的变量离开作用域后,就被自动释放
/* 当变量离开作用域,rust为我们调用一个特殊函数,这函数叫做drop自动释放内存 */ fn main(){ let str = String::from("hello"); //从此起,str开始有效 //使用str } //此作用域已结束,str不在有效
2.变量与数据交互的方式(一): 移动
- 在rust中,多个变量可以采取不同的方式与同一数据进行交互
let x = 20;
let y = x;
- 将20绑定到x,接着生成一个值x的拷贝并绑定到y,变量 x 和 y 都等于20
- 整数是已知固定大小的简单值,两个20都被放入栈中
2.1String版本
let str = String::from("hello");
let str1 = str;
- 一个指向存放字符串内容内存的指针、一个长度和 一个容量,这一组数据存放在栈上
- 右侧则是堆上存放的内容的内存部分
- 并且str赋值给str1,只拷贝了它的指针、长度和容量,并没复制指针指向的堆上数据
fn main() {
let str = String::from("自强不息");
let str1 = str;
// println!("{}", str);// error
}
- 两个数据指针指向同一个位置,有一个内存安全问题
str
和str1
离开作用域,它们都会尝试释放相同的内存,两次释放相同内存会导致内存污染- 为了确保内存安全,在执行
let str1 = str;
之后,Rust认为str
不再有效
3.变量与数据交互的方式:克隆
- 需要深度复制String中堆上的数据,而不仅仅是栈上的数据
- 需要使用到一个
clone的通用函数
fn main() {
let str = String::from("自强不息");
let str1 = str.clone();
println!("str = {},str1 = {}", str, str1); // str = 自强不息,str1 = 自强不息
}
- 非必要时,不要轻易使用这种方式,会消耗代码的资源
4.只在栈上的数据: 拷贝
- rust中,存放在栈中的数据,拷贝其实际的值是最快速的。
2.5所有权与函数
fn main() {
let str = String::from("自强不息"); //str进入作用域
takes_ownership(s); // str的值移动到函数里面,所以这里不再有效
let num = 20; // num进入作用域
makes_copy(20); // num移动函数里面,但i32是拷贝,所以在后面可继续使用x
} // num移除作用域,str的值已被移走
fn takes_ownership(some_string: String) {
// some_thing 进入作用域
println!("{}", some_string);
} // some_thing移除作用域并调用 drop 方法
fn makes_copy(some_interger: i32) {
// some_integer进入作用域
println!("{}", some_interger);
} // some_integer移除作用域
2.6返回值与作用域
fn main() {
let str = String::from("自强不息");
let (str1, len) = calculate_length(str);
println!("The length of {} is {}", str1, len); // The length of 自强不息 is 12
}
fn calculate_length(str: String) -> (String, usize) {
let length = str.len();
(str, length)
}
3.引用与借用
- 引用像一个指针,它是一个地址
- 访问储存于该地址的属于其他变量的数据,与
指针
不同,引用确保指向某个特定类型的有效值
fn main() {
let str = String::from("自强不息");
let len = calculate_length(&str);
println!("The length of {} is {}", str, len); // The length of 自强不息 is 12
}
fn calculate_length(str: &String) -> usize {
let length = str.len();
length
}
-
&str
语法让我们创建一个指向值str
的引用,但是并不拥有它,所以当引用停止使用
时,它所指向的值也不会被丢弃 -
创建一个引用的行为称为借用,只有使用权限,没有拥有权限,
借用变量不可修改
3.1可变引用
- 允许修改一个借用的值,就是
可变引用
fn main() {
let mut str = String::from("天行健,君子以");
change(&mut str);
println!("{}", str)
}
fn change(some_thing: &mut String) {
some_thing.push_str("自强不息!");
}
1.可变引用的限制
- 创建一个可变引用,我们就不能再创建对变量的引用
fn main() {
let mut str = String::from("天行健,君子以");
let str1 = &mut str;
// let str2 = &mut str; //error
// println!("{},{}", str1, str2);
}
- 防止同一时间对同一数据存在多个可变引用,限制的好处就是Rust可以再编译时就避免数据竞争,数据竞争类似于竞态条件,它由以下行为造成
- 两个或更多指针同时访问同一数据
- 至少有一个指针被用来写入数据
- 没有同步数据访问的机制
1.1允许多个可变引用-作用域
fn main(){
let mut str = String::from("自强不息");
{
let str1 = &mut str;
}
let str2 = &mut str;
}
1.2不可变引用和可变引用
- 不能在拥有不可变引用的同时拥有可变引用
- 多个不可变引用是可以的
- 不可变引用和可变引用同时存在的场景
- 不可变引用
str1
和str2
的作用域在println!
最后一次使用之后结束 - 可变引用
str3
创建和str1 & str2
作用域没重叠
- 不可变引用
fn main(){
let mut str = String::from("自强不息");
let str1 = & str;
let str2 = & str;
println!("{},{}",str1,str2);// 自强不息,自强不息
let str3 = &mut str;
println!("{}",str3);//自强不息
}
3.2悬垂引用
- 具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个
垂悬指针
- 垂悬指针是其指向的内存可能已经被分配给其他持有者
- 在rust中,编译器确保引用永远不会变成垂悬指针,编译器确保数据不会在其引用之前离开作用域
fn main(){
let reference_to_nothing = dangle();
}
/*
// error
fn dangle() -> &String{
let str = String::from("自强不息");
&str //返回字符串str的引用
}// 这里str离开作用域并被丢弃,其内存释放
*/
fn dangle() ->String{
let str = Stirng::from("自强不息");
str
}
4.Slice类型
fn main() {
let mut str = String::from("hello world");
let word = first_word(&str);
println!("{}", word); // 5
str.clear();
}
fn first_word(str: &String) -> usize {
let bytes = str.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
// b' ',表示字节字面量,它表示空格字符
if item == b' ' {
return i;
}
}
str.len()
}
4.1字符串slice
- 字符串slice是
String
中一部分值的引用
let str = String::from("hello world");
let hello = &str[0..5];
let world = &str[6..11];
println!("{}",world);//world
fn main() {
let mut str = String::from("hello world");
let hello = first_word(&str[..]);
//str.clear();// clear需要清空String,尝试获取一个可变引用,而hello使用了不可变引用
println!("The value of is {}", hello);
}
fn first_word(str: &str) -> &str {
let bytes = str.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &str[0..i];
}
}
&str[..]
}
4.1字符串字面值就是slice
- 字符串字面值被存储在二进制文件中
let str = "hello world";
- 这里的str类型就是&str,它指向二进制程序特定位置的slice
4.2字符串slice作为参数
fn first_world(st: &str) -> &str{}
4.2其他类型的slice
let arr = [1,2,5,1,6,8];
let slice = &arr[1..3];
assert_eq!(slice,&[2,5]);
- slice的类型是&[i32]