文章目录
- 前言
- 一、管理计算机内存的方式
- [二、Rust中的 move](#二、Rust中的 move)
-
-
- [Copy trait](#Copy trait)
-
- [三、Rust中的 clone](#三、Rust中的 clone)
- 总结
前言
Rust入门学习系列-Rust 的核心功能(之一)是 所有权(ownership)。引入这个概念是为了更好的管理计算机的内存。下面篇幅让我们来研究下这个功能有什么神奇之处。
一、管理计算机内存的方式
常见的编程语言中计算机内存管理方式:
- Java:Java使用Java虚拟机(JVM)来管理计算机内存。JVM有一个垃圾回收器(GC),用于自动回收不再使用的对象并释放内存。
- .NET:.NET使用托管堆来管理内存。在.NET中,对象分配在托管堆上,当对象不再使用时,垃圾回收器会自动回收内存。
- Go:Go使用垃圾回收器(GC)来管理内存。Go的GC使用标记-清除算法来识别和清除不再使用的对象,并回收相关的内存。
- JavaScript:JavaScript使用自动垃圾回收器来管理内存。JavaScript引擎会周期性地检查对象是否不再被引用,如果对象没有引用,则会将其标记为垃圾并回收相关的内存。
- Rust:Rust使用所有权系统来管理内存。在Rust中,每个值都有一个所有者,并且只有当前所有者可以访问和修改该值。当值的所有者超出作用域时,该值将被自动释放。
通过上面的几种常用语言的列举分析,所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言中,开发者必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。
所有权规则
那么所有权管理有什么规则?所有权的规则:
- Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
二、Rust中的 move
在Rust中,move操作指的是将变量的所有权从一个变量转移到另一个变量。当对某个值进行move操作时,该值将不再有效,不能再被访问或使用。
move操作通常发生在以下几种情况下:
- 将一个变量赋值给另一个变量时,会发生move操作。例如:
rust
let a = String::from("Hello");
let b = a; // move操作,a的所有权转移到b
// println!("{}", a); // 这里会出现编译错误,a已经不再有效
- 函数参数传递时,会发生move操作。例如:
rust
fn take_ownership(s: String) {
// ...
}
let s = String::from("Hello");
take_ownership(s); // move操作,s的所有权转移到函数take_ownership中
// println!("{}", s); // 这里会出现编译错误,s已经不再有效
- 函数返回值时,会发生move操作。例如:
rust
fn create_string() -> String {
let s = String::from("Hello");
s // move操作,将s作为返回值
}
let s = create_string(); // move操作,create_string函数的返回值所有权转移到s
// println!("{}", s); // 这里会出现编译错误,s已经不再有效
需要注意的是,通过clone()方法可以创建值的深拷贝,而不是move操作。例如:
rust
let a = String::from("Hello");
let b = a.clone(); // 深拷贝,a的所有权不会转移
println!("{}", a); // 正常打印 Hello
println!("{}", b); // 正常打印 Hello
除了移动操作,我们还可以借用值的引用来使用它,而不会转移所有权。借用可以是不可变的(引用为&T)或可变的(引用为&mut T)。以下是一个示例:
rust
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 通过引用借用s1值的所有权,计算长度
println!("The length of '{}' is {}.", s1, len);
// s1.push_str(", world!"); // 这一行代码将导致编译错误,因为我们只借用了s1的不可变引用
let mut s2 = String::from("hello");
change(&mut s2); // 通过可变引用借用s2的值的所有权,进行修改
println!("s2 = {}", s2);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn change(s: &mut String) {
s.push_str(", world!");
}
在这个例子中,我们将s1的引用传递给calculate_length函数,它只借用了s1的值的不可变引用来计算其长度,而没有转移所有权。我们还可以将s2的可变引用传递给change函数,它通过可变引用修改了s2的值。
Rust通过引用和所有权规则实现了借用和移动操作。通过移动操作,我们可以转移值的所有权到新的变量中。通过借用操作,我们可以共享值的引用,而不转移所有权。这种机制在Rust中确保了内存安全和线程安全。
Copy trait
下面的代码可以运行~
rust
fn main() {
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
}
Rust 有一个叫做 Copy trait 的特殊标注,可以用在类似整型这样的存储在栈上的类型上。如果一个类型实现了 Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。
那么哪些类型实现了 Copy trait 呢?你可以查看给定类型的文档来确认,不过作为一个通用的规则,任何一组简单标量值的组合都可以实现 Copy,任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型:
所有整数类型,比如 u32。
布尔类型,bool,它的值是 true 和 false。
所有浮点数类型,比如 f64。
字符类型,char。
元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。
三、Rust中的 clone
Rust中的clone是一种方法,用于创建一个数据的完全副本。它会创建一个与原始数据相同的新数据,但是新数据独立于原始数据,具有自己的所有权。
在Rust中,数据的所有权规则决定了每个值在任何给定时间点只能有一个所有者。当我们将一个值赋给另一个变量时,所有权会转移给新变量。当我们通过引用来传递值时,所有权不会转移,只是借用该值。
使用clone方法可以在不转移所有权的情况下创建数据的完整副本。这对于需要在多个地方使用同一份数据时非常有用,而不想转移所有权。
以下是一个简单的示例,演示了clone方法的使用:
rust
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
在这个例子中,我们创建一个字符串s1
,然后通过调用clone方法创建了一个s2
的副本。两个变量现在都拥有自己的所有权,并且可以独立于彼此使用。
需要注意的是,clone方法是一个昂贵的操作,因为它会对数据进行深拷贝。在处理大型数据结构时,应该谨慎使用clone,以避免性能问题。
clone方法是Rust中用于创建完整副本的方法,而不转移数据所有权。它对于需要在不同地方使用相同数据副本的场景非常有用。
总结
Rust 是一种系统级编程语言,其最重要的特性是所有权系统。这个特性可以使程序员避免许多常见的内存安全问题。
在 Rust 中,每个值都有一个所有者。这个所有者负责分配和释放值的内存。当所有者离开作用域时,它会自动释放相应的内存。
Rust 的所有权系统通过三个规则来实现:
-
唯一性规则:每个值都只能有一个所有者。当值被分配给另一个所有者时,原来的所有者将失效。这可以有效防止两个所有者同时释放同一个内存。
-
移动语义:当将值赋给另一个变量时,它将从原来的变量中移动到新的变量中。这意味着原来的变量将失效,并且不能再使用它。这确保了所有权的唯一性规则。
-
借用规则:可以通过借用来暂时地使用一个值,而不改变所有权。借用是通过引用来实现的,借用的变量称为引用。引用只是对值的一个视图,它不具备所有权。借用规则规定了如何正确地使用和管理引用。
总结来说,Rust 的所有权系统通过唯一性规则、移动语义和借用规则保证了内存的安全性和有效性。这使得 Rust 可以在不使用垃圾回收机制的情况下,实现高性能和安全的系统级编程。
参考: Rust 中文文档