讲透Rust核心概念:所有权

文章目录

    • 可行域
    • 移动和克隆
    • 函数传参
    • 引用与租借

Rust初步上手

程序在内存中运行,所有权指的也是内存的所有权,这个概念被提出的目的,就是让Rust在编译阶段能够更有效地分析内存资源,并优化管理,相当于是静态的垃圾回收。

Rust中的所有权有以下三条规则:

  • 每个值对应一个变量,变量是值的所有者。
  • 这个值在某一时刻,只能有一个所有者。
  • 若所有者不在程序运行范围,该值将被删除。

可行域

在Rust中,任何变量都有其可行域,在作用域中创建的变量,无法渗透到外面,这就是所有权的一个体现。比如在调用下面这个函数时就会报错

fn owner(){
    {
        let a = "micro";
    }
    println!("{}",a);
}

错误发生在编译期间

>rustc main.rs
error[E0425]: cannot find value `a` in this scope
--> main.rs:10:19
   |
10 |     println!("{}",a);
   |                   ^
   |
help: the binding `a` is available in a different scope in the same function                                              --> main.rs:8:13
   |
8  |         let a = "micro";
   |             ^
error: aborting due to previous erro

在编程中难免遇到一些长度不固定的变量,所以程序必须有在执行期间分配内存的能力,不能一切都指望编译期。而且这个分配,既包括新内存的发放,也包括老内存的销毁。

Rust在编译时,会自动在合适的地方添加一些释放资源的函数,从而维护了内存安全。

移动和克隆

下面这种写法大家已经司空见惯了,将5绑定给x,然后再将x绑定给y,其编译运行结果没有任何疑问,就是x=5,y=5

fn test_move(){
    let x = 5;
    let y = x;
    println!("x={},y={}", x,y)
}

但换一种数据类型,结果却报错了,说是把x的值移动给了y,所以x自己就没有了。

fn test_move2(){
    let x = String::from("micro");
    let y = x;
    println!("x={},y={}", x,y)
}

并且报错中还给出了温馨提示,说

help: consider cloning the value if the performance cost is acceptable
   |
13 |     let y = x.clone();
   |              ++++++++

即需要调用clone方法来将x值克隆给y,否则就相当于是把x的值移动给了y,所以x自己就没有了。二者的区别相当于是复制和剪切。

在rust中,一些小而直接的数据类型,在数据传递过程中,是默认克隆模式的,比如test_move1中演示的整数,这些类型还包括

  • 所有整数类型,例如 i32 、 u32 、 i64 等。
  • 布尔类型 bool,值为 true 或 false 。
  • 所有浮点类型,f32 和 f64。
  • 字符类型 char。
  • 仅以上类型数据的元组

而其他数据类型,不好意思,默认的就是移动,如果想把其他类型的x复制给y,就要用到下面的形式

fn test_clone(){
    let x = String::from("micro");
    let y = x.clone();
    println!("x={},y={}", x,y);
}

函数传参

在rust中,要知道任何表达式都可以当成是一个函数,是有返回值的。那么自然地,变量在参数传递过程中的特性,也自然要发生在函数传参时,下面的写法有报错了,要求把test_print(x)改写为test_print(x.clone())

fn test_print(s:String){
    println!("s={}", s);
}

fn test_move3(){
    let x = String::from("micro");
    test_print(x);
    println!("x={}", x);
}

但是同样地,如果传递的参数是一个基础类型的变量,那就完全没问题。

fn test_print_int(i:i32){
    println!("i={}", i);
}

fn test_move4(){
    let x = 5;
    test_print_int(x);
    println!("x={}", x);
}

引用与租借

对于复杂变量x,如果想x的值赋予y,要么选择移动,但这样x自己就没了;要么选择克隆,但这个开销就比较大。一个自然的想法就是,能不能把x的地址传给b,这样两人就可以共享一块内存区域。

这种操作rust当然是支持的,名曰引用,只需用到取地址符&,所以下面的函数可以顺利执行

fn test_ref(){
    let x = String::from("cool");
    let y = &x;
    x.push_str("l")
    println!("x={},y={}", x,y)
}

函数参数传递的道理一样。

但是,正所谓皮之不存毛将焉附,如果y在引用x的值之后,如果x的值被移动给了另外一个变量,那么y,的引用也自然就作废了。

另一方面,y只是引用到了x的地址,但并没有得到这篇内存的所有权,所以y在理论上是不可写入的,这种感觉就像是借书一样,可以随便看,但不能乱写乱画。所以下面的代码报错就是理所当然的了。

实例

fn test_borrow(){
    let x = String::from("cool");
    let y = &x;
    y.push_str("l");
    println!("{}", y);
}

但是,如果非要更改,那么也不是不可以实现,只需采用mut引用,写成下面这样就可以了,y成了一个可变引用类型的数据。

fn test_borrow2(0[
    let x = String::from("cool");
    let y = &mut x;
    y.push_str("l");
    println!("{}", y);
])

但可变引用也有一个问题,即只允许可变引用一次。最后,引用不可以作为函数的返回值,因为函数的返回值将不确定这个引用会给谁,可能导致灾难性后果。

这就是Rust语言的风格,严谨而死板,由此带来的好处则是更加安全。

你可能感兴趣的:(编程语言学习,rust,开发语言,所有权,后端,可行域,租借,移动)