让Rust在不需要gc的情况下保证内存安全
栈和堆的差异

  • 栈数据占用固定大小;堆放编译器大小未知或者大小可能变化的数据
  • 入栈快,不需要搜索内存空间,总在栈顶分配;堆上分配内存要找内存空间,并记录

    一、什么是所有权

    主流管理内存的方式分为两种
  1. gc
  2. 手动分配和释放

Rust另辟蹊径——通过所有权管理内存

  • 编译期根据一系列规则进行检查
  • 所有权机制不会带来明显的运行时开销
  • 所有权机制主要是管理堆数据

    所有权规则

  1. Rust中的每一个值都有一个被称为其 所有者owner 的变量
  2. 值在任意时刻有且只有一个所有者
  3. 所有者(变量)离开作用域时,这个值将被丢弃

    补充:String类型

    之前的数据类型都是分配在栈上的,包括字符串字面值(它是被硬编码到程序中的)
    字符串字面值的缺点
  • 不可变(适用性受限)
  • 编码时不一定能知道字符串的值,比如想要获取用户输入

String是分配到堆上的

  • 可存储编译时位置大小的文本
  • 为支持可变文本,Rust怎么做的?——C++的RAII
    • 只能在运行时在堆上申请内存:程序员主动调用String::from
    • 当处理完String时需要将内存返回给os:Rust帮我们在作用域结尾处自动调用特殊的函数drop

      二、变量和数据的交互方式

      移动Copy

      ```rust //拷贝 let x = 5; let y = x; //生成x的拷贝到y,此时栈上放入了2个5

//移动 { let s1 = String::from(“hello”); let s2 = s1; //没有拷贝堆上的数据,而是拷贝了栈上的s1 }//我们傻乎乎地自作聪明,认为Rust调用drop,s1和s2尝试释放相同内存,double free //实际上s2复制时,s1不再有效 //!!这就是移动嘛,太强了!!

  1. <a name="moNJK"></a>
  2. ### 克隆Clone
  3. 我们当然会想要一个String的深拷贝,此时clone函数就是我们想要的
  4. - clone当然开销很大啦,于是Rust不想随随便便就去克隆一个String,需要我自己主动要求
  5. 但好像基本类型像i32他们,似乎并不需要指定调用clone,也做到了克隆
  6. - 这种类型在编译时大小已知,可以存栈上,于是拷贝其实没什么大的消耗,并没有必要去要求在创建y的时候让x无效化
  7. Copy trait
  8. - 是一种特殊注解
  9. - 如果一个类型有Copy trait,那么旧的变量赋值给其他变量后,旧变量仍然可以使用
  10. - Rust禁止实现了Drop trait的类型使用Copy trait
  11. - 判断可Copy的类型:简单标量和其组合可Copy,不需要分配内存或某种形式资源可Copy
  12. - u32/bool/f64/char/各元素都是Copy的tuple
  13. <a name="sSRN7"></a>
  14. ## 三、所有权与函数、返回值
  15. 向函数传参可能会移动和复制,就和给普通变量赋值类似
  16. ```rust
  17. fn main() {
  18. let s = String::from("hello"); // s 进入作用域
  19. takes_ownership(s); // s 的值移动到函数里 ...
  20. // ... 所以到这里不再有效
  21. //如果使用s,则报错
  22. let x = 5; // x 进入作用域
  23. makes_copy(x); // x 应该移动函数里,
  24. // 但 i32 是 Copy 的,所以在后面可继续使用 x
  25. } // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
  26. // 所以不会有特殊操作
  27. fn takes_ownership(some_string: String) { // some_string 进入作用域
  28. println!("{}", some_string);
  29. } // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
  30. fn makes_copy(some_integer: i32) { // some_integer 进入作用域
  31. println!("{}", some_integer);
  32. } // 这里,some_integer 移出作用域。不会有特殊操作

返回值也可以转移所有权

  1. fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
  2. a_string // 返回 a_string 并移出给调用的函数
  3. }

四、引用和借用

我们有时不想移动来移动去的,只是想稍微用一下堆上的数据而已
——引用
!!!需要记住

  • 任意给定时间,只能有一个mut可变引用,或者,多个不可变引用
  • 引用必须有效,不要返回悬垂引用(可以尝试直接使用移动语义来返回,转移所有权)

    1、引用,用引用定义借用

  • 允许使用值,但不获取所有权;引用离开作用域时,引用指向的值也不会销毁

  • 引用没有所有权,函数使用引用参数时不需要返回值来归还所有权,因为不曾拥有~
  • 我们将获取引用作为参数称为 借用borrowing
  • 书写要点
    • 实参标记&
    • 形参&标记到类型上
  • 代码示例

    • &String s -> String s1 -> 堆”hello”
      1. fn main(){
      2. let s1 = String::from("hello");
      3. let len = get_length(&s1);
      4. }
      5. fn get_length(s: &String) -> usize {
      6. s.len()
      7. }
      我们不能直接修改借用的变量!QAQ
      ——可变引用

      2、数据竞争data race

  • 危害:未定义行为,运行时难跟踪,难以诊断

  • 产生原因

    • 两个或更多指针同时访问同一数据
    • 至少有一个指针写入
    • 没有同步数据访问的机制

      3、可变引用

  • 书写要点:实参和形参都是直接在&后加上mut

  • 限制:特定作用域中,特定数据,只能有一个可变引用
    • 阻止数据竞争data race

代码示例

  • 可变理解为写,不可变理解为读 ```rust //多于一个的写 let mut s = String::from(“hello”); let r1 = &mut s; let r2 = &mut s; //简单的修改 let mut s = String::from(“hello”); { let r1 = &mut s; }//r1离开作用域,内外都是只有一个写 let r2 = &mut s;

//多个读没问题,但读写不能共存 //多个读一个写时,具体是否报错根据具体情况而定,下面会报错 let mut s = String::from(“hello”); let r1 = &s; //ok let r2 = &s; //ok let r3 = &mut s; //damn!!! println!(“{}, {}, and {}”, r1, r2, r3); //上面的例子换成下面这样就没事了,好优化的情况下编译器会控制作用域的编排 let mut s = String::from(“hello”); let r1 = &s; //ok let r2 = &s; //ok println!(“{} and {}”, r1, r2); //之后r1 r2不会用了 let r3 = &mut s; //ok println!(“{}”, r3);

  1. <a name="m2kMA"></a>
  2. ### 4、悬垂指针
  3. - 当我有一些数据的引用时,编译器确保数据不会在引用之前离开作用域
  4. - 在不用生命周期参数的情况下,我自己制造一个悬垂,编译器会保存叫我用lifetime parameter
  5. ```rust
  6. fn dangle() -> &String{//返回一个对字符串的引用
  7. let s = String::from("hello");
  8. &s; //返回引用
  9. }//s离开作用域并被丢弃,释放内存
  10. fn main(){
  11. let reference_to_nothing = dangle();
  12. }
  13. //解决办法:直接返回一个String,所有权直接被移动出去,没有值被释放
  14. fn no_dangle() -> String {
  15. let s = String::from("hello");
  16. s
  17. }

Slice类型

是一个没有所有权的数据类型
slice允许引用集合中一段连续的元素序列,而不引用整个集合

1、字符串slice

是对String中一部分值的引用
[start, end]——左闭右开区间

  1. let s = String::from("hello world");
  2. let hello = &s[..5];
  3. let word = &s[6..];
  4. let hello_world = &s[..];

设计一个函数,返回使用空格分隔的第一个单词

  • 返回的是字符串slice,它有非常好的性质

如果上面的函数返回的不是slice,而是一个usize会怎样???

  • main函数中首先将word变量绑定到usize的值上,假设是5
  • 而后清空字符串,此时word仍是原来的usize值,还是5
  • 此时这个word无效,但程序在编译时没报错!!!!
  • 为啥我想要程序编译报错呢??

    • 我需要时刻担心索引word和s的数据是否同步,不然用过时的索引进行访问会出问题!
    • 代码可能没直接的错误,但是逻辑是错的
    • 这个时候slice就发挥作用了
      1. fn first_word(s: &String) -> usize {
      2. let bytes = s.as_bytes();
      3. for (i, &item) in bytes.iter().enumerate() {
      4. if item == b' ' {
      5. return i;
      6. }
      7. }
      8. s.len()
      9. }
      10. fn main() {
      11. let mut s = String::from("hello world");
      12. let word = first_word(&s); // word 的值为 5
      13. s.clear(); // 这清空了字符串,使其等于 ""
      14. // word 在此处的值仍然是 5,
      15. // 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效!
      16. }
      回头看slice到底好在哪儿,如下图代码
  • slice的数据结构存储了自己的开始位置和长度

  • 当调用slice版本first_word时,编译器会保证指向String的引用持续有效

    • 报immutable borrow问题:cannot borrow s as mutable because it is also borrowed as immutable
    • 本质还是:拥有某个值的不可变引用后,就不能再获取一个可变引用
      • clear要清空String,因此尝试获得可变引用;而word已经表明引用不可变了
        1. fn first_word(s: &String) -> &str {
        2. let bytes = s.as_bytes();
        3. for(i, &item) in bytes.iter().enumerate() {
        4. if item==b' ' {
        5. return &s[0..i]; //返回的不是一个usize索引,而是一个切片
        6. }
        7. }
        8. &s[..];
        9. }
        10. fn main() {
        11. let mut s = String::from("hello world");
        12. let word = first_word(&s);
        13. s.clear(); // 错误!
        14. }

        2、惊!!!字符串字面量就是slice!

        1. let s = "Hello world"; //这里s的类型就是&str
  • s类型是&str:是指向二进制程序特定位置的slice

  • 可以看出为什么字符串字面量不可变:&str是不可变引用

3、老司机字符串传参

first_word是个好函数,可惜之前写的只能作用于String,是堆上分配可动态改变长度的那种,但我也想用在字符串字面量上 ——可以将字符串slice作为参数,从而适用于String和&str
下面代码很重要

  1. fn first_word(s: &str) -> &str { ... }
  2. fn main() {
  3. let my_string = String::from("hello world");
  4. // first_word 中传入 `String` 的 slice
  5. let word = first_word(&my_string[..]);
  6. let my_string_literal = "hello world";
  7. // first_word 中传入字符串字面值的 slice
  8. let word = first_word(&my_string_literal[..]);
  9. // 因为字符串字面值 **就是** 字符串 slice,
  10. // 这样写也可以,即不使用 slice 语法!
  11. let word = first_word(my_string_literal);
  12. }

4、其他类型slice

和字符串工作方式一样
下面的slice变量类型就是 &[i32]

  1. let a = [1, 2, 3, 4, 5];
  2. let slice = &a[1..3];