rust学习五、认识所有权
在的中译版中,作者用了30页的篇幅来叙述这个问题。如作者所言,所有权是学习rust语言的基础,不把握这个,无需继续往下,以是,这是初学rust就必须会的。
正是所有权概念和干系工具的引入,Rust才能够在没有垃圾接纳机制的条件下保障内存安全。
一、变量的存储方式和赋值方式
要进入rust所有权范围讨论问题,那么必须先理解RUST的变量的存储方式和赋值方式
rust出于各种目的,规定变量可以存放在栈和堆上:
[*]栈-存放哪些编译时期就知道大小的。通常存储那些简单的数据范例,例如整数、浮点、布尔、字符、成员范例都是整数、浮点、布尔、字符之一的元组
[*]注意这是一个FILO(先辈后出,大概是后进先出)范例的,好似堆碟子,反而最上面的最先用。
[*]堆-存放那些编译时期无法知道其大小的。例如String(字符串)
栈变量转赋
对于栈变量而言,把a赋值给b,仅仅是一种在栈中复制数据的过程-快速且本钱小。
let a=10;
let b=a; 上面这个代码中,就是把10复制一份给b。
堆变量转赋
但是堆差异,这个就是所有权的问题的来源,见后文。
堆变量和java的类变量是相识的存储方式,都是用一个地址指向实际的存储区域,如下图(来自书本):
https://img2024.cnblogs.com/blog/1177268/202410/1177268-20241029180457437-574972590.png
所有权问题就是堆变量问题。
二、所有权规则是怎样产生的?所有权规则是什么?
2.1、规则怎么来?
要理解所有权,就需要从rust的设计目的谈起,否则难于理解有些概念。
按照rust官方的说法,rust要保证内存安全(一定会开释掉),同时还需要保证高效(不能用java 那样的gc管理器),同时还不能太繁琐(像C++那样手动开释)
以是,他们想到了一个主意:一个变量应该用完就自动开释(通常是立刻开释)
通过设这种设定,那么就解决了一些问题:
[*]不会内存泄漏
[*]不需要借助额外的垃圾收集器,增大了用于体系编程的可行性
但这会引发一个问题,怎么知道需要被移除的变量没人用了?
像java那样使用引用计数器之类的?但是现在不搞那一套,就需要定个规矩:一个变量一定要有所有者;而且任意时刻只能有一个所有者
和前面提到的结合起来,就是rust著名的所有权规则:
[*]一个值(不是变量)一定要有所有者
[*]而且任意时刻,一个值只能有一个所有者
[*]值所有者离开作用域后,持有的值会被立刻开释
由于任意时间一个值只有一个所有者,以是一旦所有者离开返回,就可以简单“高效”地执行开释操作,不用担心还被谁用了。
规则就是为了达到这个目的。
这个规则有一个瑕疵:按照rust官方的说法,有个drop步伐会立刻做这个事情,那么这某种程度上会降低性能。
但是现代高级语言都没有解决这个问题,哪怕C++之类的,以是这个就不算是一个问题了。大家都装糊涂吧,大概某天cpu和操作体系变化了之后,可以解决这个问题。
以是,这应该是一个当前情况下,权衡后的可接受方案。
2.2、规则怎么得到保证?
规则定了,那么怎样保证这些规则可以得到遵守?
1.明白作用域的
如果是有明白作用域的,例如{}大概函数,那么很好理解:
fn test(){
let s:String=String::from("种瓜得豆");
println!("{}",s);
}绝大部分语言都是这么规定,以是能够立刻理解!离开函数大概明白的作用域,就应该要开释掉。
2.在一个作用域之内
参考书中的经典例子:
let s1=String::from("锦绣中华");
let s2=s1;
println!("{}",s1); 通不过编译!
let s1=String::from("锦绣中华");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
12 | let s2=s1;
| -- value moved here
13 | println!("{}",s1);
| ^^ value borrowed here after move第一次接触这个,我有一点震动,是由于两个没有想到:
1.怎么会不可?
2.编译器就能够解决这个问题,而不是在运行时发生。编译器够锋利的。
现在再回过头看下这个编译错误提示,提示大意是如许的:
由于s1是String范例,以是发生的移动情况。String并没有实现Copy trait(范例特性)(意思就是不能通过复制完成值的转赋)
当指定s2=s1的时间,s1的值已经转给了s2(s1已经没有值了)
当再次使用s1的时间,这就是一种典型的错误举动:值被移动后计划借用
通过这个提示我们可以看到:在一个作用域之内,如果一个变量转赋给另外一个变量的时间,会发生一件事情:值会从一个变量转到另外一个变量上
为什么要这么做? 由于只有如许才能保证前面提到的第二个原则:任意时刻只能有一个变量拥有某个值
回头再看编译提示,我们还知道一点:雷同s1如许的变量只以是会发生这个值转移情况,是由于s1是String,默认没有实现Copy范例特性。
反过来说,如果String实现了Copy范例特性,那么就不会发生这种编译错误。
2.3、规则带来的其它问题
虽然三个规则保证了目的,但是会带来不少问题(贫苦),比较典型的问题就是:
一个变量的值被移走后,那么再次使用原来的变量就变得贫苦了。 由于需要再次移动值。如果如许写代码,太冗余啰嗦,谁也受不了!
以下是一个奇怪的例子。
fn main(){
let mutaddress:String=String::from("福建福州");
println!("{}",address);
print_str(address);
//再用address,那么久会报错,如果要不报错,则必须
//插入诸如 address=xxx之类语句,把值移回来,或者重新赋值
println!("新地址:{}",address) ;//报错
}
fnprint_str(s:String){
println!("{}",s);
}
在上例中,如果为了避免println!("新地址:{}",address)报错,必须需要在这句话之前做一些事情。如许无疑太贫苦了。
如果按照上面这种方法,那么rust就没有存在的意义,由于一门语言不但要考虑性能、安全等,也需要考虑工程效果:不能让工程师烦
为了解决这个问题,rust给出了三个解决方案
[*]不可变引用-通过某种方式标志这是一种借用,值所有权没有转移。借用完成后会自动归还。当前借用不能修改值
[*]可变引用 -通过某种方式标志这是一种借用,值所有权没有转移,借用完成后会自动归还。这个期间,其它变量不能同时用这个值。但是当前借用能修改这个值
[*]切片引用-只借用了部分(也可以是全部),可以细分为不可变切片和可变切片
通过这3个方案,编译器已经帮工程师默默地完成了借用和自动归还的操作,工程师不需要再操心了。
借用是怎样实现的,借用原书的2幅图
图_引用
https://img2024.cnblogs.com/blog/1177268/202410/1177268-20241030185748299-2060273846.png
图_切片引用
https://img2024.cnblogs.com/blog/1177268/202410/1177268-20241030185824278-1438292979.png
原书还列出两个引用原则:
[*]在任何一段给定的时间内,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用
[*]引用总是有效的
后文演示这几个例子
2.3.1不可变引用
fn main() {
let name = String::from("岳飞");
//引用,被引用多少次都无所谓
print_me(&name);
print_me(&name);
let n1 = &name;
let n2 = &name;
println!("n1={},n2={}", n1, n2);
}
书写格式: &s
name可以同时被不可变借用多次。
这意味着,并发情况下可以共用一个值。
2.3.2可变引用
书写格式: &mut s
fn mut_borrow() {
let mut s = String::from("飞翔");
let s1 = &mut s;
println!("{}", s1);
s1.push_str("在太空!");
println!("{}", s1);
let s2 = &mut s;
println!("{}", s2);
//println!("{}",s1); //在已经借给s2的情况下,再使用s1会报错 -- first borrow later used here
let s3 = &mut s;
s3.push_str("星光灿烂");
println!("{}", s3);
let s4 = &mut s;
let s5 = &mut s;
let s6 = &s;
}
由于只能同时有一个可变引用,以是并发是一个问题。
2.3.3切片引用
书写格式:
&s[..] -- 等同于s&s -- 取从n到m-1的部分,其中n>=0&s[..n] -- 取0到n-1的部分&s -- 取从n及其之后的所有特殊地,字符串切片范例写成 &str。
同时界说多个不可变切片,并可同时用
fn show_string_slice(){
let name=String::from("ABCDEF GHIJKLMN");
let s1=&name;
let s2=&name;
println!("s1={},s2={}",s1,s2);
}
其它切片演示:
可变的数组切片
fn test_mut_slice(){
let mut numbers = ;
let slice: &mut = &mut numbers; // 获取整个数组的可变切片
slice = 10; // 修改切片中的第一个元素,这也会修改原始数组
println!("{:?}", numbers); // 输出:
}
三、小节
[*]rust的值的所有权规则是一个非常独特的内容,其它语言暂时没有如许的情况
[*]rust的所有权有一点难于理解,但务必理解,由于这是继续的基础,此关不通,不要考虑后面的学习内容
[*]rust的所有权,可以算是一种相对高效的主动垃圾接纳机制,是一种可以接受的妥协
[*]rust提供了多种引用方式,使得同时共享一个值变得可能,例如引用,切片引用
[*]rust的编译器默默地干了很多的事情
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]