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);
    }
  • 语法糖:一些简写

    语法糖
    从初始值开始 &s10...2 &s1...2
    到末尾值结束 &s13...s1.len() &s13...
    整个字符串 &s10...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

相关推荐
Scott9999HH6 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社7 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海7 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖8 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a11177610 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao10 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
doiito11 小时前
【AI 应用】从“外国人味”到地道中文:kokoroi-rs v0.1.2 架构升级深度解析
ai·rust·系统设计
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf