二-内存模型及所有权和引用、借用

1. 内存模型1

内存模型,heap和stack的区别,GC方面和go的区别

基本同go一样,分为堆内存、栈内存。栈内存函数退出时会自动释放,大小有限,一般是比较"小"的变量存到栈上。

比较"大"的或者大小动态变化的会分配到堆上。同时为了使用这个值,需要在栈上有一个变量指向这个堆的地址。

和go的区别在于,go是GC系统在运行时动态记录堆内存的使用情况,自己来清理,用户完全不用管。c这类完全交给用户处理,总是容易出错。

Rust介于两者之间,通过所有权和生命周期的机制,在编译阶段识别到堆内存的使用情况,等到不再被使用时,会自动清理。

2. String类型

整个高级点的东西,继续说明上面的内存问题,基于这种类型讲所有权、借用才有意义

这里的String类似go里面的字符切片,var stringSlice []byte,结构也类似,有一个结构体 包含指针、cap、len 三字表头,然后指针指向堆内存的一个 "底层数组"。

根据结构可知,既然涉及到堆内存,就关系到内存释放问题,对于Rust就是通过所有权和生命周期机制,"自动"释放内存。

使用示例如下:

rust 复制代码
// 注意 mut 声明的才可以继续进行修改操作
let mut string1 = String::from("String hello");
string1.push_str(", world");
println!("{}", string1);

3. 所有权,引用、可变引用、借用,

3.1 先看一个示例

rust 复制代码
fn main() {
    let mut s = String::from("Hello ");
    s.push_str("world");
    // 把s做为参数传给另一个函数,这个在go里很常见,会拷贝s的皮,但是用一套底层数组
    just_print(s); //  - value moved here
    println!("{}", s);
}

fn just_print(s: String) {
    // do some thing
    println!("{}", s);
}

这时学go同学惊呆了,后面s竟然是无效的了,感觉很不合理,明明有这个变量,竟然会无效;学c++的同学,可能联想到,如果这个指针,在最后一行打印之前,被Delete了,那的确会异常。

所以有GC语言的同学,如果接受不了,存在变量,但是无效的情况,可以结合C++想一下,这不就是写了个bug。

3.2 所有权转移

上文示例,其实就是所有权转移,Rust通过一个堆内的"对象",只能有一个变量拥有它的所有权这种方式,就能很好的追踪何时可以自动GC,在编译的时候,追踪这个对象的所有权变量,以及这个变量的使用情况,就可以在不用的时候释放内存。

rust 复制代码
let string1 = String::from("a");
let string3 = string1; // 这里会发生所有权转移,此时string1就无效了 println!("{}", string1);报错。

这样的限制,避免一个堆内存被多个变量指向,一是写代码的人容易勿删导致悬垂引用,二是只有一份所有权的情况下,编译器就可以盯着有所有权的这个变量的生命周期的范围,从而知道啥时候到期进行自动GC

3.3 mut

先说说这个,这个其实比所有权 应用什么的简单很多,就是控制能不能修改而已。

通过示例可以知道,是不是可变的关键在于用let绑定或者传参的时候,有没有mut,和之前的那个变量是不是可变的无关。

rust 复制代码
// string3.push_str("afs"); // 这时不能
let mut string4 = string3;
string4.push_str("!!! change to mut.");
println!("{}", string4);

3.4 引用、可变引用、借用

所有权每次都要来回转移,只能转移到一个上面,很是麻烦,尤其是函数传参的情况,作为参数传进去之后,如果不再返回回来,编译器就认为这个堆内存的生命就到这了,就给GC了。

所以增加了引用的概念,让事情简单一些,也叫借用borrow,之前所有权转移叫 move。

rust 复制代码
let string4 = get_then_return(string4);// 这里甚至有整成了不可变的,所以mut相对随意,甚至如果所有的都加mut就和其他语言一样了,但是引用那里有限制。
println!("{}", string4);
let s = &string4;

可变引用又会导致数据竞争问题,

3.5 引用借用的关系

这两个词经常一起出现,后面看了Rust程序设计第二版才清楚。引用是对&这种方式的表述,借用则是表示了他真正的道理:所有借用,必须归还,即借用不会影响原有的生命周期,反而受原有变量的生命周期控制。

引用是一个用于指代另一个值的值,需要里面的数据时,可以解引用。这个在go语言中不存在,因为go只有值传递,没有引用传递。

rust 复制代码
fn main() {
    let a = 10;
    let ai = &a;
    assert_eq!(*ai, a);
}
相关推荐
Swift社区1 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht1 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht1 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20241 小时前
Swift 数组
开发语言
stm 学习ing2 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc3 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe4 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin4 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python