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


    1. 内存模型1

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

    基本同go一样,分为堆内存、栈内存。栈内存函数退出时会自动释放,大小有限,一般是比较“小”的变量存到栈上。
    比较“大”的或者大小动态变化的会分配到堆上。同时为了使用这个值,需要在栈上有一个变量指向这个堆的地址。
    和go的区别在于,go是GC系统在运行时动态记录堆内存的使用情况,自己来清理,用户完全不用管。c这类完全交给用户处理,总是容易出错。
    Rust介于两者之间,通过所有权和生命周期的机制,在编译阶段识别到堆内存的使用情况,等到不再被使用时,会自动清理。

    2. String类型

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

    这里的String类似go里面的字符切片,var stringSlice []byte,结构也类似,有一个结构体 包含指针、cap、len 三字表头,然后指针指向堆内存的一个 “底层数组”。
    根据结构可知,既然涉及到堆内存,就关系到内存释放问题,对于Rust就是通过所有权和生命周期机制,“自动”释放内存。

    使用示例如下:

    // 注意 mut 声明的才可以继续进行修改操作
    let mut string1 = String::from("String hello");
    string1.push_str(", world");
    println!("{}", string1);
    
    • 1
    • 2
    • 3
    • 4

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

    3.1 先看一个示例

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这时学go同学惊呆了,后面s竟然是无效的了,感觉很不合理,明明有这个变量,竟然会无效;学c++的同学,可能联想到,如果这个指针,在最后一行打印之前,被Delete了,那的确会异常。
    所以有GC语言的同学,如果接受不了,存在变量,但是无效的情况,可以结合C++想一下,这不就是写了个bug。

    3.2 所有权转移

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

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

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

    3.3 mut

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

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

    // string3.push_str("afs"); // 这时不能
    let mut string4 = string3;
    string4.push_str("!!! change to mut.");
    println!("{}", string4);
    
    • 1
    • 2
    • 3
    • 4

    3.4 引用、可变引用、借用

    所有权每次都要来回转移,只能转移到一个上面,很是麻烦,尤其是函数传参的情况,作为参数传进去之后,如果不再返回回来,编译器就认为这个堆内存的生命就到这了,就给GC了。
    所以增加了引用的概念,让事情简单一些,也叫借用borrow,之前所有权转移叫 move。

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

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

    3.5 引用借用的关系

    这两个词经常一起出现,后面看了Rust程序设计第二版才清楚。引用是对&这种方式的表述,借用则是表示了他真正的道理:所有借用,必须归还,即借用不会影响原有的生命周期,反而受原有变量的生命周期控制。
    引用是一个用于指代另一个值的值,需要里面的数据时,可以解引用。这个在go语言中不存在,因为go只有值传递,没有引用传递。

    fn main() {
        let a = 10;
        let ai = &a;
        assert_eq!(*ai, a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    springMVC下载文件
    我的周刊(第053期)
    轨道交通工业控制系统信息安全:分析与展望
    Learning Deep Convolutional Networks for Demosaicing
    解决from d2l import torch as d2l报pandas,numpy的相关问题
    Java String类
    李沐software 跟着敲代码报错
    如何选择正确的哈希算法?
    实现性别平等,我们绝不能再等132年
    chromium线程模型(1)-普通线程实现(ui和io线程)
  • 原文地址:https://blog.csdn.net/yks0527/article/details/134488989