首页 技术杂谈 正文
  • 本文约2349字,阅读需12分钟
  • 182
  • 0

Rust - 所有权

摘要

Rust所有权系统,确保内存安全的同时提供性能优势。通过独特的借用规则和作用域生命周期,Rust代码既高效又安全,无需垃圾收集器。

1、所有权

1.1、所有权概念

  • 所有权是Rust最独特的特性,它让Rust无需GC就可以保证内存的安全
  • Rust的核心特性就是所有权
    • 所有程序在运行时都必须管理它们使用计算机内存的方式
    • 有些语言有垃圾收集机制,在程序运行时,它们会不断的寻找不再使用的内存
    • 在其他语言中,程序员必须显式地分配和释放内存
    • Rust采用了第三种方式
    • 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检测的规则
    • 当程序运行时,所有权特性不会减慢程序的运行速度

1.2、栈内存和堆内存

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

1.3、所有权存在的原因

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

1.4、所有权规则

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

1.5、String类型

  • 通过string类型学习所有权
  • 字符串字面值,在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里
    • 速度快、高效。是因为其不可变性
  • String类型,为了支持可变性,需要在heap上分配内存保存编译时未知的文本内容
    • 操作系统必须在运行时来请求内存,通过string::from来实现
    • 当用完String之后,需要使用某种方式将内存返回给操作系统
  • Rust对于GC采用了不同的方式:对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交还给操作系统(drop函数)
fn main() {
    // 创建一个string类型的字符串
    // 因为string类型的字符串是可以修改的
    let mut name = String::from("Hello");
    name.push_str(", World");
    println!("{}", name);
}

1.6、变量和数据交互的方式:移动(Move)

  • 多个变量可以与同一个数据使用一种独特的方式来交互
  • 在Rust中,当数据被赋值给另一个变量时,所有权会转移,如果数据类型没有COPY特征,那么原来的变量将不在有效
let s1 = String::from("hello");
let s2 = s1; // 所有权转移给 s2
// 此时 s1 不再有效

1.7、Copy特征

  • 一些简单的数据类型实现了Copy的接口,那么它们可以"无成本"地被赋值
  • 基本的数据类型
    • 整数、布尔、浮点、字符、元组(都实现了copy接口)
let x = 5;
let y = x; // x 的值被复制到 y
// 此时 x 仍然有效

1.8、所有权与函数

  • 在语义上,将值传递给函数和把值赋给变量是类似的
    • 将值传递给函数将发生移动或赋值
fn main() {
    let s = String::from("Hello World");

    take_ownershop(s);

    let x = 5;

    makes_copy(x);

    println!("x:{}",x);

}

fn take_ownershop(some_string: String){
    println!("{}", some_string);
}

fn makes_copy(some_number:i32) {
    println!("{}",some_number);
}

1.9、返回值与作用域

  • 函数在返回值的过程中同样也会发生所有权的转移
  • 一个变量的所有权总是遵循同样的模式
    • 把一个值赋给其他变量时就会发生移动
    • 当一个包含heap数据的变量离开作用域时,它的值就会被drop函数清理,除非数据的所有权移动到另一个变量上
fn main() {
    let s1 = gives_ownership();

    let s2 = String::from("hello");

    let s3 = takes_and_gives_back(s2);

}

fn gives_ownership() -> String{
    let some_string = String::from("hello");
    some_string
}

fn takes_and_gives_back(a_string:String) -> String{
    a_string
}
评论