rust 学习3 ownership

文档 https://doc.rust-lang.org/stable/book/ch04-01-what-is-ownership.html

所有权(ownership)

所有权是Rust的一个核心概念,是一系列Rust用于内存管理的规则。
在Rust中,数据的存储的地方,主要有堆Heap栈Stack
常规的数据(简单理解为长度已知且固定的数据)存储在栈中,一些复杂的数据结构(简单理解为长度但为固定的可变数据)则存储在堆中。

所有权规则 Ownership Rules

在Rust中,所有权的规则,主要有以下三条。

  • 每一个值都会有一个owner
  • 每一个值在某一时刻有且只有一个owner
  • 当owner离开作用域后,值会被销毁

变量作用域 Variable Scope

变量作用域,简单理解为变量的有效范围,比如在写代码时,需要在不同地方定义不同的变量,不同的变量会有其不同的作用范围,rust的变量作用域,大抵如此。
最常见的,便是用花括号定义一个作用域。

fn main() {                      // s is not valid here, it’s not yet declared
    let s = "hello";   // s is valid from this point forward
	  // do stuff with s
}        

简言之

  • 当变量(owner )走入作用域定,它变得有效了
  • 当变量(owner )走出作用域后,它变得无效了
Rust是怎么销毁无效变量(owner)呢?

当一个变量(owner)变得无效后,rust会帮忙调用drop()方法,来销毁(owner)。当然在代码中也可以自己收到销毁,只是没太大必要。

变量和数据的移动交互

当变量被赋值给另外一个变量时,会导致所有者的改变。
试想一下,如果一个值,被赋值给一个变量后,这个变量又被赋值给另一个变量,会发生什么呢?

数值
fn main() {                      
    let x = 5;
    let y = x;
    println!("x: {}, y: {}", x, y);
}        
   Compiling variables v0.1.0 (D:\learning\rust\variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.35s
     Running `target\debug\variables.exe`
x: 5, y: 5
常规字符串
fn main() {                      
    let x = "5";
    let y = x;
    println!("x: {}, y: {}", x, y);
}        
   Compiling variables v0.1.0 (D:\learning\rust\variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.37s
     Running `target\debug\variables.exe`
x: 5, y: 5
可变字符串
fn main() {                      
    let s1 = String::from("hello");
    let s2 = s1;
    println!("s1: {}, s2: {}", s1, s2);
}         
   Compiling variables v0.1.0 (D:\learning\rust\variables)
error[E0382]: borrow of moved value: `s1`                                                                  
 --> src\main.rs:4:32
  |
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 |     println!("s1: {}, s2: {}", s1, s2);
  |                                ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

For more information about this error, try `rustc --explain E0382`.
error: could not compile `variables` (bin "variables") due to previous error                               

 *  The terminal process "C:\Users\Leon\.cargo\bin\cargo.exe 'run', '--package', 'variables', '--bin', 'variables'" terminated with exit code: 101.          

以上的三个例子中,当s2被赋值为String::from(“hello”),会导致编译错位,为什么呢?
这时因为rust在管理内存时,不同的数据会被分别存放于堆或者栈中,赋值操作仅发生在栈中。

数据存储方式

对于已知长度为固定的值,rust把值存在栈中;
而对于长度未知,不固定的值,rust则把数据存在堆中,并且在栈中存储一份指向堆中数据的指针。

变量赋值时所有权的变化

当变量被复值时,会在栈中生成一个值,如果变量被赋值给另一个变量, 则生产变量的值的一份copy,并指向新的变量。
比如数值的赋值例子

let x = 5;
let y = x;

在栈中,把5赋值给x,当执行y=x时,则生产5的一份复制的值,并赋值给y。

同理,对于以下代码,也是一样的。

 let x = "5";
 let y = x;

所以,x,y分别为两个值(虽然相同)的所有者。

然后,对于以下代码,则稍微不同。

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

当String::from(“hello”)被赋值给x时,内存中的情况时这样的。

  1. 在堆中生成一个字符串,值为“hello”
  2. 在栈中生成一个变量x,并指向堆中的“hello”
  3. 当y = x,在堆中复制一份的x,那么,x,y都会成为"hello"的所有者,会跟所有者的规则冲突,因此产生编译错误。
    具体如下图所示
    在这里插入图片描述
    要这么解决这个问题呢?把堆中的数据复制一份即可
fn main() {                      
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1: {}, s2: {}", s1, s2);
}        
[Running] cd "d:\learning\rust\variables\src\" && rustc main.rs && "d:\learning\rust\variables\src\"main
s1: hello, s2: hello

如果写过Java代码的应该知道,当变量指向一个类时,如果把变量赋值给另一个变量,一旦数据发生改变,两个变量指向的数据都会方式改变。
特别是把此类变量传给不同函数时,很容易不小心把元数据进行改动,导致后面要花好多时间去检查。

public class Util {
    public static void main(String[] args) {
        List<String> path = new ArrayList<>();
        List<String> path1 = path;
        System.out.println("path = " + path);
        System.out.println("path1 = " + path1);

        path1.add(" allen");
        System.out.println("path = " + path);
        System.out.println("path1 = " + path1);
    }
}
C:\sandbox\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2\lib\idea_rt.jar=60780:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2\bin" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath C:\Users\Leon\IdeaProjects\java-demo\target\classes;C:\Users\Leon\.m2\repository\com\google\code\gson\gson\2.8.9\gson-2.8.9.jar file.Util
path = []
path1 = []
path = [ allen]
path1 = [ allen]

或许,这是rust为了在一定程度上减少数据被随意修改,造成不可测的异常而做的吧。

总结

  • ownership时rust用于管理内存的方式
  • 变量只有在自己的作用域内有效,超出作用域后,变量被销毁
  • 长度固定的不可变数据,如数值,字符串,变量和值都存储在栈中,变量可以随意被赋值给别的变量,并生成原值的复制给新的变量,原变量的所有者不变。
  • 长度不固定的可变数据,如数值,字符串数据结构,变量存储在栈中,而实际的值则存储在堆中。简单地把变量赋值给另外一个变量,指挥在栈中复制一份同样指向元数据的指针,导致堆中的一份数据有了多个所有着,产生冲突。解决办法为复制一份堆中的数据,如使用clone方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值