Rust学习记录--C4 Rust所有权

C4 Rust所有权

所有权

  • 所有权是rust最独特的特性,它让rust无需GC(垃圾收集器)就可以保证内存安全。

什么是所有权

  • rust的核心特性就是所有权
  • 所有程序在运行时都必须管理他们使用计算机内存的方式
    • 有些语言有垃圾收集机制,在程序运行时,他们会不断寻找不再使用的内存
    • 在其他语言中,程序员需要显式的分配和释放内存,比如c或者c++
  • Rust采用第三种方式
    • 内存通过一个所有权系统来管理,其中包含一组编译器在编译时检查的规则
    • 当程序运行时,所有权特性不会减慢程序的运行速度

栈内存stack和堆内存heap

  • 在rust中使用stack和heap有很大的不同
  • stack和heap都是可用的内存,但是结果很不相同
对比 stack(栈) heap(堆) 备注
存储数据 按值得接收顺序存储,按照相反的顺序移除(后进先出,LIFO, last in first out)
添加数据:入栈,移除数据:出栈
所有存储在stack上得数据必须拥有已知得固定的大小 编译时大小未知或者运行时大小可能发生变化的数据必须放在heap上
指针是已知固定大小,可以把指针存放在stack上
heap内存组织性差一些,会请求一定数量的空间
把值压入stack上不叫分配 在heap上进行分配:操作系统在heap中找到一块足够大的空间,把它标记为再用,并返回一个指针,也就是这个空间的地址
把数据压到stack上比在heap上分配快的多 在stack上,操作系统不需要寻找用来存储新数据的空间,位置用于在stack的顶端 在heap上分配空间需要更多工作:操作系统首先需要找到一个足够大的空间来存放数据,然后要做好记录方便下次分配
访问数据 访问heap的数据比stack的要慢,因为通过指针才能找到heap中的数据 对于现代的处理器,由于缓存的缘故,如果指令在内存中跳转的次数越少,速度越快
处理速度 数据存放距离近,处理速度快->stack上 数据之间距离较远,处理速度慢->heap上 heap上分配大量的空间也是需要时间的
函数调用 代码调用函数时,值被传入到函数(也包含指向heap的指针)。函数本地的变量被压到stack上,当函数结束时,这些值会从stack上弹出

所有权存在的原因

  • 所有权解决的问题
    • 跟踪代码的哪些部分在使用heap的哪些数据
    • 最小化heap上的重复数据量
    • 清理heap上未使用的数据以避免空间不足
  • 一旦懂了所有权就不用经常想stack和heap了
  • 管理heap数据是所有权存在的原因

所有权规则/内存与分配

所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除

变量作用域

String类型

  • 基本概念

    • 字符串类型,能够存储在编译时未知数量的文本
    • 存储在堆上->用于研究String类型是如何回收的
    • 字符串字面值:程序中手写的"ssss",他们是不可变的。因为两者的内存分配方式不同
  • 如何创建String类型的值

    • 使用from函数
    rust 复制代码
    // ::表示from是String类型下的函数
    let mut s = String::from("hell0");
    s.push_str(", world!");
  • 变量与数据的交互方式

    • 移动 Move

      • 对于存在堆上的数据,String的移动如下:
        这个s2=s1只是单纯的赋值了栈 上的ptr,len和capacity
      rust 复制代码
      let s1 = String::from("hell0");
      // 这个s2=s1只是单纯的赋值了栈上的ptr,len和capacity
      let s2 = s1;
      println!("{}",s2);
      // s1已经失效了,编译器会自动报错
      println!("{}",s1);
      • 类似浅拷贝
      • rust不会自动创建数据的深拷贝
    • 克隆 Clone(针对heap上的数据)

      • 如果真的像对heap上的String数据进行深度拷贝,可以使用Clone方法
      rust 复制代码
      fn main() {
          let s1 = String::from("hello");
          let s2 = s1.clone();
          println!("{}", s2);
          println!("{}", s1);
      }
      • clone是将stack和heap的数据都做了个copy ,比较消耗资源
    • 复制(针对stack上的数据)

      • 原因:x是整数类型,在编译期间就确认了大小,数据存储在stack中,复制操作非常快速
      rust 复制代码
      fn main() {
          let x = 2;
          let y = x;
          // 这里x和y都有值,都有效
          println!("{}, {}", x,y);
      }
      • copy trait
        • 如果一个类型实现了copy trait,旧的变量在复制后仍然可用
        • 如果一个类型或者该类型的一部分实现了Drop trait,rust旧不允许它再去实现copy trait
        • 一些用于copy trait的类型
          • 任何简单的标量的组合类型都是可以copy的
          • 所有整数,bool, char,浮点类型
          • Tuple,如果所有字段都是可copy的
            • (i32,i32)是
            • (i32, String)不是

所有权与函数

  • 将值传递给函数,要不会发生复制要不会发生移动

    • 将String值传给函数也会发生Move,原始值也会报错
  • 函数在返回值的过程中同样也会发生所有权的转移

    s1只是为了获取到s的所有权,当s进去到函数并且函数执行完成后s就被销毁了

    • 一个变量的所有权遵循的模式:
      • 把一个值赋给其他变量是会发生 move/copy(heap相关的变量会move,stack相关的会copy)

      • 当一个包含heap数据的变量离开作用域,它的值就会被drop函数清理(或者数据的所有权移动到另一个变量上)

      • 看个例子

        rust 复制代码
        // 观察caculate_length1和caculate_length2有什么不同?
        fn caculate_length1(s: String) -> (String, usize) {
        	// 这里只是借用,下面会提到
        	let len =s.len(); 
        	return (s, len);
        } 
        
        fn caculate_length2(s: String) -> (String, usize) { 
        	// 会编译报错,显示s.len()会报错,s已经被move,所有s.len()没办法执行
        	return (s, s.len()); 
        }
  • 如何让函数使用某个值,但不获得其所有权(我的理解是函数用了某个值,但是这个值在函数结束后任然可以正常被调用)?

    • 使用引用

引用与借用

引用

  • 实现效果对比

    • 没有使用引用
    rust 复制代码
    fn main() {
        let s1 = String::from("hello");
        let (s2, length) = caculate_length(s1);
        println!("s2: {}, length: {}", s2, length);
    }
    
    fn caculate_length(s: String) -> (String, usize)
    {
        let len = s.len();
        return (s, len);
    }
    • 使用引用
    rust 复制代码
    fn main() {
        let s1 = String::from("hello");
        let length = caculate_length(&s1);
        println!("Length: {}", length);
    }
    
    fn caculate_length(s: &String) -> usize
    {
        return s.len();
    }
    • & 表示引用:允许引用某些值而不取得其所有权
      s = &s1

借用

  • 把引用作为函数参数的行为叫借用???->&s1
  • 是否可以修改借用的东西?
    • 不行
    • 引用默认情况下是不可变的 ->可以切换为可变引用

可变引用

  • 变成可变引用后代码如下

    rust 复制代码
    fn main() {
        let mut s1 = String::from("hello");
        let length = caculate_length(&mut s1);
        println!("Length: {}", length);
    }
    
    fn caculate_length(s: &mut String) -> usize
    {
        s.push_str(", world");
        return s.len();
    }

可变引用的限制

  • 在特定作用域,对某一块数据,只能有一个可变的引用

    • 编译时就可以防止数据竞争
    • 以下三种情况会引发数据竞争
      • 两个或者多个指针访问同一个数据
      • 至少有一个指针用于写入数据
      • 没有使用任何机制来同步对数据的访问
  • 可以通过创建新的作用域,来允许非同时的创建多个可变引用

  • 不可以同时拥有一个可变引用和一个不可变引用

  • 多个不可变引用是可以的

悬空引用(Dangling References)

  • 悬空指针:一个指针引用了内存的某个地址,而这块内催可能已经被释放或者分配给其他人使用了。
  • 在rust中编译器可保证引用永远不是悬空引用

引用的规则

  • 在任何给定时刻,只能满足下列条件之一:
    • 一个可变的引用
    • 任意数量的不可变引用
  • 引用必须一直有效

切片

  • 切片 slice :不持有所有权的数据类型
  • 问题:编写一个函数
    • 接收字符串作为参数
    • 返回在这个字符串中找到的第一个单词
    • 如果函数没有找到空格,那么整个字符串就被返回 // 找到空格位置或者整个字符串长度
rust 复制代码
// 初级版本: // 找到空格位置或者整个字符串长度 
fn main() {
    let mut s1 = String::from("he llo ");
    let index = get_first_word_index(&s1);
    println!("the first word index is {}", index);
}

fn get_first_word_index(str: &String) -> usize{
    // turn to byte
    let bytes = str.as_bytes();
    // bytes.iter().enumerate() 将bytes转化成turple类型
    // i表示index,item表示具体的值
    for (i, &item) in bytes.iter().enumerate(){
        // b' ' 表示一个空字符的byte值
        if item == b' '{
            return i;
        }
    }
    return str.len();
}
  • 上述存在的问题,index 是不随s1的变化而变化的(我觉得很正常)

字符串切片

  • 字符串切片是指向字符串中一部分内容的引用

  • 形式:[开始索引 ... 结束索引] 表示[开始,结束)左开右闭

  • 存储两个内容:指针(指向起始元素位置)和length

    rust 复制代码
    fn main() {
        let mut s1 = String::from("he llo ");
        let first = &s1[0..2];
        let last = &s1[3..6];
        // 输出he
        println!("the first word is {}", first);
        // 输出llo
        println!("the last word is {}", last);
    }
  • 语法糖:一些简写

    语法糖
    从初始值开始 &s1[0...2] &s1[...2]
    到末尾值结束 &s1[3...s1.len()] &s1[3...]
    整个字符串 &s1[0...s1.len()] &s1[...]
  • 范围索引必须发生在有效的UTF-8字符边界内。

rust 复制代码
// 进化版本: // 返回第一个单词
fn main() {
    let mut s1 = String::from("he llo ");
    let index = get_first_word_index(&s1);
    // 加这一行会报错,因为在上一行已经是不可变的借用,所以这里不能改成可变的借用
    s1.clear();
    println!("the first word index is {}", index);
}
// 切片的类型是&str, &str是不可变的
fn get_first_word_index(str: &String) -> &str{
    // turn to byte
    let bytes = str.as_bytes();
    // bytes.iter().enumerate() 将bytes转化成turple类型
    // i表示index,item表示具体的值
    for (i, &item) in bytes.iter().enumerate(){
        // b' ' 表示一个空字符的byte值
        if item == b' '{
            return &str[..i];
        }
    }
    return &str[..];
}
  • 将字符串切片作为参数传递

    rust 复制代码
    fn first_word(s: &String)->&str{}

    转化为

    rust 复制代码
    fn first_word(s: &str)->&str{}
    • 在使用String时,直接创建一个完整的String切片来调用该函数

其他类型的切片

2026/1/6

相关推荐
im_AMBER几秒前
Leetcode 97 移除链表元素
c++·笔记·学习·算法·leetcode·链表
海奥华23 分钟前
Golang Channel 原理深度解析
服务器·开发语言·网络·数据结构·算法·golang
代码游侠5 分钟前
学习笔记——MQTT协议
开发语言·笔记·php
渡我白衣11 分钟前
计算机组成原理(13):多路选择器与三态门
开发语言·javascript·ecmascript·数字电路·计算机组成原理·三态门·多路选择器
@zulnger12 分钟前
python 学习笔记(异常对象)
笔记·python·学习
其美杰布-富贵-李12 分钟前
x-transformers 完整学习笔记
笔记·学习·transformer
HUST13 分钟前
C语言 第十讲:操作符详解
c语言·开发语言
星火开发设计14 分钟前
链表详解及C++实现
数据结构·c++·学习·链表·指针·知识
田里的水稻17 分钟前
matlab_绘图线条颜色显示和点的形状显示
开发语言·matlab
木木木一20 分钟前
Rust学习记录--C6 枚举与模式匹配
rust