07:Rust所有权

Rust所有权

所有权使得Rust无需垃圾回收器(garbage collector)即可保证内存安全。

Rust中的栈(Stack)和堆(Heap)

  • 栈:以放入值顺序存储值,以相反的方向取出值。(后进先出),栈中所有数据必须占用已知且固定的大小,在编译未知大小或者大小可以变化的数据,要存储在堆上。
    • 栈中增加数据叫做进栈,移出数据叫做出栈。
  • 堆:堆中存储数据,要先请求一定大小的空间,并标记为已经使用,返回一个表示该地址位置的指针。(这个过程叫做在堆上分配内存)
  • 指针可以存储在栈上,但实际需要时,必须访问指针。指针大小是固定的,所以不会定义为==“分配”==
  • 入栈相比在堆上分配内存较快,因为入栈无需分配新的内存,内存会存储在栈顶,而堆上分配内存会先找到足够存放数据的内存空间,然后为分配内存做准备。
  • 访问堆上的内存需要使用指针访问,所以访问数据时栈相比堆也较快,在堆上分配大量的空间会消耗时间,数据距离较近(栈)相比较远(堆)中能更好的工作。
  • 当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。

而所有权的出现就时为了更加有效的管理堆中的数据。

所有权规则

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

变量的作用域

fn main{
let s = "hello";
}
  • 这个变量在main函数内部都有效,这个就是它的作用域。

String 类型

  • string是可变的,而字面量是不可变的。
fn main() {
    let mut s = String::from("hello");

    s.push_str(", world!"); // push_str() 在字符串后追加字面值

    println!("{}", s); // 将打印 `hello, world!`
}

  • 字符串字面量是通过硬编码存储在程序中,但是他们是不可变的。
  • 可以使用from函数来进行基于字符串字面变量来创建String,同时可以使用s.push_str("xxxx")在字符串之后添加新的字符串字面值。
  • 关于string和str的区别1:
  • 关于string和str的区别2:

内存和分配

下面是官方文档的描述:
在这里插入图片描述

  • String类型在堆上分配在编译时大小未知的内存存放内容。
    • 在运行时向分配器请求内存
    • 需要处理完String时将内存返回给分配器的方法。
  • Rust中内存回收在变量离开作用域时就会自动释放。
fn main() {
    {
        let s = String::from("hello"); // 从此处起,s 开始有效

        // 使用 s
    }                                  // 此作用域已结束,
                                       // s 不再有效
}

Rust中释放内存代码自动使用drop函数,使用位置在"}"处使用。在C++中又称做资源获取即初始化

变量和数据交互的方法(一):移动

let x = 5;
let y = x;
  • 多个变量可以和一个数据以不同的方式交互。
let s1 = String::from("hello");
let s2 = s1;

但是string和整形变量的交互方式并不相同,string组成部分有三个,指向存放字符串内容内存的指针,一个长度,一个容量。
下面是官方文档:
在这里插入图片描述
在这里插入图片描述
简而言之,Rust中在拷贝过程中只拷贝了指针,长度,容量,而没有直接拷贝堆上数据。

  • 然而,这种拷贝过后在释放内存的过程中,需要了解的是,二者指向同一个位置(数据指针),所以在释放过程中有可能造成二次释放错误,但是Rust中做出了约束,释放过程中只会释放s2,而将s1看作不再有效。
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{}, world!", s1);
}

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:28
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 | 
5 |     println!("{}, world!", s1);
  |                            ^^ value borrowed here after move

For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership` due to previous error

Rust禁止无效引用。
在其它语言中,将指针、长度、容量进行拷贝而不对数据拷贝叫做浅拷贝,对应的有深拷贝,而在Rust中不仅做出了浅拷贝,同时使第一个变量无效,这种方式叫做移动
在这里插入图片描述
这种方式,有点类似指针指向同一个地址。
但是s1此时是无效的。

变量和数据交互方式(二):克隆

如果需要深拷贝,需要使用一个函数clone,但是这会导致运行速度降低。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);
}

这时,处在堆上的数据是被复制的。复制方式如图4.3.(官方文档)

只在栈上的数据:拷贝

与String不同,整型变量为大小已知的变量,存储在栈上,下面代码中:

fn main() {
    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);
}

  • 会正确的编译出来,也就是说,此时x、y都是有效的变量。
  • Rust中变量有Copy trait、Drop traittrait、并且这两种trait是不能够相互兼容的,类似string的无法使用copy trait
  • 作为通用的标准,任何一组简单标量值组合都能够实现Copy,任何不需要分配内存或某种形式资源类型可以实现Copy,下面是常见的Copy类型:
    • 所有的整数所有整数类型,比如 u32。
    • 布尔类型,bool,它的值是 true 和 false。
    • 所有浮点数类型,比如 f64。
    • 字符类型,char。
    • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。

所有权和函数

  • 示例使用注释展示变量何时进入和离开作用域
fn main() {
  let s = String::from("hello");  // s 进入作用域

  takes_ownership(s);             // s 的值移动到函数里 ...
                                  // ... 所以到这里不再有效

  let x = 5;                      // x 进入作用域

  makes_copy(x);                  // x 应该移动函数里,
                                  // 但 i32 是 Copy 的,所以在后面可继续使用 x

} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
  // 所以不会有特殊操作

fn takes_ownership(some_string: String) { // some_string 进入作用域
  println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
  println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

在这里插入图片描述

下面是错误代码,在some_string结束时会使用drop释放内存。

返回值和作用域

fn main() {
  let s1 = gives_ownership();         // gives_ownership 将返回值
                                      // 移给 s1

  let s2 = String::from("hello");     // s2 进入作用域

  let s3 = takes_and_gives_back(s2);  // s2 被移动到
                                      // takes_and_gives_back 中,
                                      // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
  // 所以什么也不会发生。s1 移出作用域并被丢弃

fn gives_ownership() -> String {           // gives_ownership 将返回值移动给
                                           // 调用它的函数

  let some_string = String::from("yours"); // some_string 进入作用域

  some_string                              // 返回 some_string 并移出给调用的函数
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域

  a_string  // 返回 a_string 并移出给调用的函数
}

  • 这里s1得到了give_ownership()函数中的返回值

  • s3得到了take_and_gives_back()函数中的返回值。

  • 此时需要注意的是:s2已经被移动到takes_and_gives_back中,后面再使用时就使用不了了。
    若在主函数末尾添加println!("{}",s2);会报错,显示borrow of moved value: s2

  • 如果想要使用一个值但是不反回所有权,可以使用元组

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() 返回字符串的长度

    (s, length)
}

  • 这种使用元组来返回多个值中的数据,在Rust中有更好的方法,叫做引用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值