[简记]rust中借用与可变借用理解
背景
rust语言是最近几年比较火的一门语言,被称为编程界的“原神”,就是喜欢玩的人的都说好,去给没玩过的人说特别好玩,不喜欢的人看不惯这种到处吹的,中立派也觉得就那样,甚至看着Rust吹到处吹,就反着黑下[1]
介绍
所有权
所有权(ownership)是 Rust 用于如何管理内存的一组规则。规则如下[2]
Rust 中的每一个值都有一个 所有者(owner)。
值在任一时刻有且只有一个所有者。
当所有者(变量)离开作用域,这个值将被丢弃。
借用
我们将创建一个引用的行为称为 借用(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指针“悬空了”。如下图所示:
和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中使用类似,可以操作并修改了
注意
如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 s 的可变引用的代码会失败:
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; //此处编译失败,因为不能二次创建对该变量的引用
println!("{}, {}", r1, r2);
}
我们 也 不能在拥有不可变引用的同时拥有可变引用
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