[简记]rust中借用与可变借用理解

33

背景

rust语言是最近几年比较火的一门语言,被称为编程界的“原神”,就是喜欢玩的人的都说好,去给没玩过的人说特别好玩,不喜欢的人看不惯这种到处吹的,中立派也觉得就那样,甚至看着Rust吹到处吹,就反着黑下[1]

介绍

所有权

所有权(ownership)是 Rust 用于如何管理内存的一组规则。规则如下[2]

  1. Rust 中的每一个值都有一个 所有者owner)。

  2. 值在任一时刻有且只有一个所有者。

  3. 当所有者(变量)离开作用域,这个值将被丢弃。

借用

我们将创建一个引用的行为称为 借用(borrowing),使用前至符号`&`

可变

变量默认是不可改变的(immutable)。当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。可以使用 修饰关键词 `mut` 在变量声明时,标识为可变。

所有权的javaer理解

比如以下定义初始化字符串代码

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    s1.trim();//此处报错
}

会定义出两个变量(虽然叫变量,但是是不可变变量)s1和s2,其结果如下,s1和s2都在栈上开辟空间,指向字符串。定义出s1,指向字符串"hello",当执行`let s2 = s1`时,字符串"hello"的所有权被转移了,转移给s2。那就意味这s2拥有了所有权,s1没有字符串所有权。当调用s1的方法时,编译器会提示错误,因为s1指针“悬空了”。如下图所示:

Three tables: tables s1 and s2 representing those strings on the
stack, respectively, and both pointing to the same string data on the heap.
Table s1 is grayed out be-cause s1 is no longer valid; only s2 can be used to
access the heap data.

和java的相比,这里有所区别,如以下代码:

public class Main{
  public static void main(String[] args) {
    String s1 = "hello";
    String s2 = s1;
    s1.substring(1);//此处正常执行
  }
}

定义出的s1和s2都是能引用字符串"hello"而不报错。

这里就涉及到一个很大前置约定或者规则区别(除此之外有非常多和java有所区别的语言特征),rust的变量默认情况下不能随便简单的引用的,必须得申明(或者由程序员在代码中表达这个意图,而不模糊),才能多次使用。那怎么申明呢?那就是使用借用

借用

借用很好理解,就是临时借用下,用完变量就要归还。如下代码:

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1; //此处'&'符号就是借用
    s1.trim();//此处正常执行
}

将s1指向的字符串借用给s2, 此时s1还是能够操作,因为所有权还是归s1。

可变引用

可变借用意味着可以对引用的对象进行修改。如下代码:

fn main() {
    let mut s1 = String::from("hello");//这里增加了mut修饰关键词
    let s2 = &mut s1; //此处'&'符号就是借用
    s2.push_str(", world");;//此处正常执行
}

经过定义为可变借用,则后续使用就和java中使用类似,可以操作并修改了

注意
  1. 如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 s 的可变引用的代码会失败:

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

    let r1 = &mut s;
    let r2 = &mut s; //此处编译失败,因为不能二次创建对该变量的引用

    println!("{}, {}", r1, r2);
}
  1. 我们 也 不能在拥有不可变引用的同时拥有可变引用

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

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    let r3 = &mut s; // 报错

    println!("{}, {}, and {}", r1, r2, r3);
}

总结

由于rust没有像java那种方式通过内存模型实现gc,而是通过类似引用计数方式,但又不完全是引用计数(更像是作用域引用)方式实现gc(脱离作用域后调用drop,进行垃圾回收)。为了实现这种方式,就需要程序员在开发过程考虑内存分配,并且进行手动标记变量的可变与不可变,和相应作用域。这对程序员来说无疑是痛苦的,需要考虑更多因素,或者编程心理负担。

参考

[1]. 为什么说 Rust 是编程语言界的原神 https://www.sohu.com/a/738546662_355140

[2]. Rust 程序设计语言 https://kaisery.github.io/trpl-zh-cn/title-page.html