rust学习五 ownership所有权

所有权是rust最核心的特性。所有的编程语言都必须管理程序在运行时使用的内存,有的程序使用垃圾回收策略释放内存,有的程序则必须手动的分配和释放内存,而rust选择了第三种方式:内存是通过所有权系统和一组规则来管理的,编译器在编译时检查这些规则。当程序运行时,没有任何所有权特性会减慢程序的运行速度。

一、堆与栈

堆和栈都是内存的一部分, 栈按照获取值的顺序储存值,按照相反的顺序删除值,后进先出。将栈想象成盘子,每次都是将最新的盘子放在最上方,取盘子的时候也都是取最上方的。将数据入栈、出栈称为 pushpop 。 因为栈无论存取都只操作顶端的元素,所以访问速度很快。此外由于所有栈数据都是大小已知、长度固定的,这也能加速栈的访问速度。

而大小未知,或者长度可变的数据都无法存储在栈上,这类数据就只能放在堆上。堆得组织性较差:我们要将数据放到堆上时,必须申请一块空间,操作系统会分配一块大小合适的内存,并将这块内存的地址指针返回给我们。这一过程称为 allocating 。由于指针是大小已知,长度固定的数据, 它可以存储在栈上。 之后我们就可以通过这个指针找到这块内存的数据了。

访问堆数据要比栈元素慢很多,因为要先访问在栈上的指针,在通过指针才能找到堆数据。设想一下,在一家餐馆里,一个服务员从许多桌子上点菜。在移到另一张桌子之前,将当前桌子的菜单全部获取是最有效的。从桌子A取一个菜单,然后从桌子B取一个菜单,然后再从A取一个菜单,然后再从B取一个菜单,这将是一个慢得多的过程。同样,如果处理器处理离其他数据较近的数据(因为它在栈上),而不是离得较远的数据(因为它在堆上),处理器可以更好地完成工作。此外在堆上分配大量空间也需要时间。

当代码调用一个函数时,传递到函数中的值(可能包括指向堆上数据的指针)和函数的局部变量将被压入栈。当函数结束时,这些值将从栈中弹出。跟踪代码的哪些部分使用了堆上的哪些数据,最小化堆上的重复数据量,以及清理堆上未使用的数据,以免耗尽空间,这些都是所有权所解决的问题。一旦理解了所有权,就不需要经常考虑堆栈和堆,但知道管理堆数据是所有权存在的原因 ,可以帮助解释为什么它是这样工作的。

二、所有权

所有权规则

  1. Rust中的每个值都有一个对应的变量,叫做它的所有者。
  2. 在某一个时刻一个值只能有一个所有者。
  3. 当所有者超出作用域时,它的值会被销毁。

变量作用域

rust的作用域与变量有效性之间的关系与其他编程语言是相似的:当变量进入作用域时,它是有效的,直到变量离开作用域。

{                     // s is not valid here; it's not yet declared
    let s = "hello";  // s is valid from this point forward
    
    // do stuff with s
}                     // this scope is now over, and s is no longer valid

String类型

let s = String::from("Hello");

双引号:: 是一个操作符,它允许我们去命名空间这个在String下的特定的from 函数,而不是使用其他的命名方式, 如 String_from。

String类型是可变的,而字符串是不可变的:

let mut s = String::from("Hello");
s.push_str(", world"); 

let s2 = "hello"; // 不可变
println!("{}", s);  // this will print 'hello, world'

因为String类型的数据是大小未知,长度可变的,它是存储在堆上的,所以本章使用String类型的数据作为示例来学习rust所有权。

内存与分配

两种数据类型的不同,是由它们在内存中的不同的存储方式决定的。字符串是不可变的,在编译时,字符串类型的数据对于编译器来说是大小可知,长度固定的,它会存储在栈上,所以字符串的访问速度是非常快的。但是字符串是不可变的,在程序运行中如果想要改变文本字段,我们就要使用String类型了。String类型由此会存储在堆上。

数据存储在堆上,这就意味着:

  1. 程序必须在运行时向操作系统申请内存分配。
  2. 我们需要在使用完String数据后释放内存。

第一步我们在调用代码 String::from 的时候,会实现需要请求的内存。绝大多数编程语言的实现方式都是类似的。

但是在第二步内存回收中体现了不同语言之间的差异:

部分语言例如 Python会使用垃圾回收系统(garbage collector GC)。 CG会保持追踪和清除不在使用的数据,从而释放内存。

没有GC的话,如C/C++就需要我们去识别哪些数据不在使用, 在代码里显式的,手动释放内存。这点是很难完全正确的做到的。如果我们忘记释放某个变量,会导致内存浪费。提前释放又会导致变量失效。多次释放则会出现bug。我们很难精确的匹配每一次的分配和释放。

相比于前两种,Rust是一旦一个变量超出了它的作用域,rust会自动调用drop函数回收内存。 drop函数在右花括号处会被自动调用。

三、所有权对变量赋值的影响

Move

let x = 5;
let y = x;  // x still vaild

let s1 = String::from("Hello");
let s2 = s1;  // s1 no longer be vaild
  1. s2 = s1 的操作, s2只复制s1在栈内的指针信息,从而指向堆内的数据。
  2. rust的所有权系统会使s1失效,从而不会出现s1,s2都指向同一个堆数据的情况,因为这会导致双重释放。
  3. s2 = s1 只复制了栈信息,没有复制堆信息,很像如Python语言的浅复制,但是s1同时也失效了,我们将 s2 = s1称为移动(move) 。 数据的所有权从s1移动到了s2。 这也更好的解释s1的失效。

Clone

有时候我们想要的就是完完全全的复制一个变量, 类似于深拷贝,在rust中称为克隆(clone)

s1 = String::from("Hello");

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

clone()会复制栈和堆上的数据。当看到对clone的调用时,就知道正在执行一些专制的代码,而且这些代码的开销可能很昂贵。这是一种视觉指示,表明某些不同的事情正在发生。

Copy

let x = 5;
let y = x;  // x still vaild

println!("x = {}, y = {}", x, y)
  1. 因为x,y都是编译时大小已知的整数类型,完全存储在堆栈中,因此可以快速复制实际值
  2. 这意味着我们没有理由在创建变量y后阻止x的有效性。
  3. 换句话说,这里的深复制和浅复制没有区别,所以调用clone与通常的浅复制没有什么不同,我们可以把它省去

rust中可copy的数据类型:

  1. 所有的整型数据
  2. 布尔数据: true, false
  3. 字符串类型的数据。
  4. 浮点型数据。
  5. 元组类型, 但仅仅是元组内的元素都可复制时才可以。

四、所有权与函数

函数中传参与所有权的转移

传递一个值给函数与给一个变量赋值是相似的。

给函数传参将会进行move或copy的操作,move之后变量就会失效。

fn main() {     
    let s = String::from("hello");  // s comes into scope     takes_ownership(s);             
                                    // s's value moves into the function...
                                    // ... and so is no longer valid here     
    let x = 5;                      // x comes into scope     
    makes_copy(x);                  // x would move into the function,                                     
                                    // but i32 is Copy, so it's okay to
                                    // still use x afterward 
} // Here, x goes out of scope, then s. But because s's value was moved,   
  // nothing special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope     
    println!("{}", some_string); 
} // Here, some_string goes out of scope and `dropìs called. The backing   
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope     
    println!("{}", some_integer); 
} // Here, some_integer goes out of scope. Nothing special happens.

函数返回值与所有权的转移

返回值也会转移所有权。

fn main() {
    let s1 = gives_ownership();  // gives_ownership moves its return value into s1
    
    let s2 = String::from("Hello");  // s2 comes into scope
    
    let s3 = takes_and_gives_back(s2);  // s2 is moved into takes_and_gives_back, which alse
                                        // moves its return value into s3
    
} // here, s3 goes out of scope and is dropped.

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string    // return and moves out to the calling function
}

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

变量的所有权每次都会遵循以下原则:

  1. 赋值给另一个变量会转移所有权
  2. 当一个含有堆数据的变量超出作用域时,值会被drop函数清除。除非数据的所有权已经转移给了另一个变量

此外函数可以通过元组返回多个值:

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() returns the length of a String 
    
    (s, length)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值