rust所有权
Rust所有权(Ownership)是Rust语言中的一个核心概念,它解决了许多系统编程语言中存在的内存安全问题。所有权规则确保了在任何给定时间,只有一个可变引用或多个不可变引用可以访问数据。通过这种方式,Rust编译器可以在编译期间捕获并拒绝数据竞争等未定义行为。
rust所有权定义:一个值只能被一个变量所拥有,且同一时刻只能有一个所有者,当所有者离开作用域,其拥有的值被丢弃,内存得到释放。
rust所有权规则
-
一个值只能被一个变量所拥有,这个变量被称为所有者(Each value in Rust has a variable that’s called its owner)。
-
一个值同一时刻只能有一个所有者(There can only be one owner at a time),也就是说不能有两个变量拥有相同的值。所以对应刚才说的变量赋值、参数传递、函数返回等行为,旧的所有者会把值的所有权转移给新的所有者,以便保证单一所有者的约束。
-
当所有者离开作用域,其拥有的值被丢弃(When the owner goes out of scope, the value will be dropped),内存得到释放。
// 1. 所有权唯一性
let x = String::from("hello"); // x 是 "hello" 字符串的所有者
// 2. 所有权转移
let y = x; // 所有权从 x 转移到 y
// 此时 x 已经无效
// 3. 借用
let z = &y; // z 借用了 y 的不可变引用
println!("{}", z); // 打印 "hello"
let mut w = String::from("world");
{
let p = &mut w; // p 借用了 w 的可变引用
p.push_str("!"); // 修改了被借用的值
}
println!("{}", w); // 打印 "world!"
Copy语义
如果值实现了 Copy trait,那么赋值或传参会使用 Copy 语义,相应的值会被按位拷贝(浅拷贝),产生新的值。
rust数据结构实现copy
我们先来看下如下代码(代码来源极客时间rust第一课):
fn is_copy<T: Copy>() {}
fn types_impl_copy_trait() {
is_copy::<bool>();
is_copy::<char>();
// all iXX and uXX, usize/isize, fXX implement Copy trait
is_copy::<i8>();
is_copy::<u64>();
is_copy::<i64>();
is_copy::<usize>();
// function (actually a pointer) is Copy
is_copy::<fn()>();
// raw pointer is Copy
is_copy::<*const String>();
is_copy::<*mut String>();
// immutable reference is Copy
is_copy::<&[Vec<u8>]>();
is_copy::<&String>();
// array/tuple with values which is Copy is Copy
is_copy::<[u8; 4]>();
is_copy::<(&str, &str)>();
}
fn types_not_impl_copy_trait() {
// unsized or dynamic sized type is not Copy
is_copy::<str>();
is_copy::<[u8]>();
is_copy::<Vec<u8>>();
is_copy::<String>();
// mutable reference is not Copy
is_copy::<&mut String>();
// array / tuple with values that not Copy is not Copy
is_copy::<[Vec<u8>; 4]>();
is_copy::<(String, u32)>();
}
fn main() {
types_impl_copy_trait();
types_not_impl_copy_trait();
}
通过上面代码我们可以知道哪些数据默认实现copy trait。 总结如下:
- 原生类型,包括函数、不可变引用和裸指针实现了Copy;
- 数组和元组,如果其内部的数据结构实现了Copy,那么它们也实现了Copy
- 可变引用没有实现Copy
- 非固定大小的数据结构,没有实现Copy
Copy与Clone的区别
Copy
是浅拷贝,在你赋值或者传参时,值会自动按位拷贝。
Clone
是深拷贝,在你赋值或者传参时,需要调用clone方法,
Copy Trait:
Copy
Trait 用于那些存储在栈上的简单标量类型,如i32
、f64
等,以及完全由Copy
数据组成的复合类型。- 实现了
Copy
Trait 的类型会在赋值或函数传递时直接进行按位内存复制,这是一个廉价的操作。 - 对于
Copy
类型,不会发生所有权转移,原值和新值都可以同时使用。 Copy
只能应用于不需要分配内存或资源的类型,因为复制必须是一个完全无副作用的操作。
Clone Trait:
Clone
Trait 用于那些存储在堆上的数据结构,如String
、Vec
等,以及任何拥有资源的类型。- 实现了
Clone
Trait 的类型会在复制时执行深层的内存分配和数据复制操作。 - 对于
Clone
类型,复制会产生一个完全独立的新值,原值和新值都拥有各自的资源。 Clone
可以应用于任何类型,只要该类型提供了深度复制所需的逻辑。
示例:
// Copy 示例
let x = 42;
let y = x; // 直接复制 x 的值
println!("x = {}, y = {}", x, y); // 输出 "x = 42, y = 42"
// Clone 示例
let s1 = String::from("hello");
let s2 = s1.clone(); // 克隆 s1 的内容到新的 String 实例
println!("s1 = {}, s2 = {}", s1, s2); // 输出 "s1 = hello, s2 = hello"
在第一个例子中,x
是一个整数,实现了 Copy
Trait。因此当我们将 x
赋值给 y
时,只是简单地复制了 x
的内存值,而不涉及任何资源分配或所有权转移。
在第二个例子中,s1
是一个 String
类型,它存储在堆上。当我们调用 clone()
方法时,它会在堆上重新分配一块内存,并将 s1
的内容深度复制到新分配的内存中。这样,s1
和 s2
就拥有了完全独立的内存空间,修改其中一个不会影响另一个。
一般来说,Copy
用于简单的值语义,而 Clone
用于复杂的数据结构和资源的复制。Copy
的性能开销更小,但它有严格的限制条件;而 Clone
更加通用,但也更昂贵。
在 Rust 中,Copy
是一个特殊的标记 Trait,编译器会自动为满足条件的类型实现它。而 Clone
则需要手动实现,或者通过 #[derive(Clone)]
自动派生。
总的来说,在需要复制值时,应该优先考虑使用 Copy
。如果类型无法实现 Copy
(例如包含堆分配的数据),那么就应该使用 Clone
。在性能敏感的场景下,也应该尽量避免不必要的克隆操作。
Move语义
赋值或者传参会导致值Move,所有权被转移,一旦所有权转移,之前的变量就不能访问。
具体来说,move
发生在以下几种情况:
- 变量绑定
let x = String::from("hello");
let y = x; // x 的值被移动到 y,x 不再拥有字符串的所有权
在这个例子中,x
的值(一个 String
实例)被移动到了 y
。这是因为 String
是一个在堆上分配内存的类型,它的所有权必须是唯一的。当 x
的值被移动到 y
后,x
就失去了该值的所有权,因此无法继续使用。
- 作为参数传递
let s = String::from("hello");
take_ownership(s); // s 的值被移动到函数中
当一个值作为参数传递给函数时,它的所有权也会被移动到函数内部。在函数结束后,该值将被丢弃。
- 返回值
fn create_string() -> String {
let s = String::from("hello");
s // s 的所有权被移动到函数返回值
}
函数的返回值也会发生所有权的移动。在上面的例子中,s
的所有权被移动到了返回值中。
move
语义可以确保在任何时候,一个值都只被一个变量拥有。这样一来,就避免了发生多个变量同时访问和修改同一值的情况,从而防止了数据竞争等未定义行为。
Borrow语义
Borrow语义允许一个值的所有权,在不发生转移的情况下,被其它上下文使用。
Rust对可变引用的使用也做了严格的约束:
- 在一个作用域内,仅允许一个活跃的可变引用。所谓活跃,就是真正被使用来修改数据的可变引用,如果只是定义了,却没有使用或者当作只读引用使用,不算活跃。
- 在一个作用域内,活跃的可变引用(写)和只读引用(读)是互斥的,不能同时存在。
只读Borrow
只读借用,也称为不可变借用,允许多个不可变引用同时引用同一个值。这些引用只能读取值,而不能修改它。
let x = 5;
let y = &x; // 创建一个不可变引用
let z = &x; // 可以有多个不可变引用
println!("{} {} {}", x, y, z); // 5 5 5
可变borrow
可变借用允许创建一个可变引用,通过该引用可以读写被引用的值。但是,在同一作用域内,只能存在一个可变引用。
let mut x = 5;
let y = &mut x; // 创建一个可变引用
*y += 1; // 通过可变引用修改 x 的值
println!("{}", x); // 6
针对特殊场景提供多个所有者
比如在rust如何实现双向链表、DAG、多个线程要访问同一块内存等?针对特殊场景,rust提供了智能指针RC、ARC、RefCell、Cell等来解决这些问题。 智能指针(Smart Pointers)是一种封装了指针并增加了额外元数据和功能的数据结构。它们在底层使用了裸指针,但提供了更高级别、更安全的抽象,帮助我们自动管理资源并防止常见的内存安全问题。Rust标准库中提供了几种常用的智能指针。
- Box
: Box
是一个在堆上分配内存的智能指针。它拥有数据的所有权,当Box
离开作用域时,它会自动释放所包裹的堆内存。Box
通常用于存储一些在编译期无法确定大小的数据,或者避免过多的数据拷贝。
let x = Box::new(5);
println!("x = {}", x); // 使用 *x 来解引用
- Rc
和Arc :Rc
(Reference Counted)和Arc
(Atomic Reference Counted) 提供了共享所有权的功能。它们通过引用计数跟踪资源的所有者数量,当没有所有者时自动释放资源。Rc
用于单线程场景,Arc
则用于多线程场景(原子操作更昂贵)。
use std::rc::Rc;
let x = Rc::new(vec![1, 2, 3]);
let y = x.clone(); // 增加引用计数
println!("x before = {:?}", x);
println!("y = {:?}", y);
- RefCell
和Rc<RefCell :> RefCell
可以被认为是一种内部可变性(Interior mutability)的编译时机制。它包裹了一个可变数据,并在运行时执行借用规则检查,而不是编译时检查。通常与Rc
结合使用,可以实现在多个位置对数据进行可变借用。
use std::rc::Rc;
use std::cell::RefCell;
let x = Rc::new(RefCell::new(vec![1, 2, 3]));
x.borrow_mut().push(4); // 可变借用x中的数据
println!("x = {:?}", x);
- Mutex
和Arc<Mutex :> Mutex
(Mutual Exclusion)是一种用于线程安全的并发编程原语。它提供了一个用于互斥访问共享数据的机制。通常与Arc
结合使用,可以在多个线程间安全地共享和修改数据。
use std::sync::{Arc, Mutex};
use std::thread;
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let mut children = vec![];
for _ in 0..3 {
let data_clone = Arc::clone(&data);
children.push(thread::spawn(move || {
let mut value = data_clone.lock().unwrap();
value.push(4);
}));
}
for child in children {
child.join().unwrap();
}
println!("data = {:?}", data);
这些智能指针不仅提供了自动资源管理和线程安全等功能,同时也满足了Rust的所有权规则和借用规则,保证了内存安全。