Rust中的智能指標是什么
智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標,是指標的進一步發展
指標(pointer)是一個包含記憶體地址的變數的通用概念,這個地址參考,或 ” 指向”(points at)一些其 他資料 ,參考以 & 符號為標志并借用了他們所 指向的值,除了參考資料沒有任何其他特殊功能,它們也沒有任何額外開銷,所以在Rust中應用得最多,
智能指標是Rust中一種特殊的資料結構,它與普通指標的本質區別在于普通指標是對值的借用,而智能指標通常擁有對資料的所有權,并且可以實作很多額外的功能,
Rust智能指標有什么用,解決了什么問題
它提供了許多強大的抽象來幫助程式員管理記憶體和并發,其中一些抽象包括智能指標和內部可變性型別,它們可以幫助你更安全、更高效地管理記憶體,例如Box<T>
用于在堆上分配值,Rc<T>
是一種參考計數型別,可以實作資料的多重所有權,RefCell<T>
提供內部可變性,可用于實作對同一資料的多個可變參考
它們在標準庫中定義,可以用來更靈活地管理記憶體,智能指標的一個特點就是實作了Drop和Deref這兩個trait,其中Drop trait中提供了drop方法,在析構時會去呼叫,Deref trait提供了自動解參考的能力,讓我們在使用智能指標的時候不需要再手動解參考了
Rust有哪些常用智能指標
Box<T>
是最簡單的智能指標,它允許你在堆上分配值并在離開作用域時自動釋放記憶體,Rc<T>
和Arc<T>
是參考計數型別,它們允許多個指標指向同一個值,當最后一個指標離開作用域時,值將被釋放,Rc<T>
不是執行緒安全的,而Arc<T>
是執行緒安全的,
內部可變性型別允許你在不可變參考的情況下修改內部值,Rust中有幾種內部可變性型別,包括Cell<T>
,RefCell<T>
和UnsafeCell<T>
,
Cell<T>
是一個內部可變性型別,它允許你在不可變參考的情況下修改內部值,Cell<T>
只能用于Copy
型別,因為它通過復制值來實作內部可變性,RefCell<T>
也是一個內部可變性型別,它允許你在不可變參考的情況下修改內部值,與Cell<T>
不同,RefCell<T>
可以用于非Copy
型別,它通過借用檢查來確保運行時的安全性,UnsafeCell<T>
是一個底層的內部可變性型別,它允許你在不可變參考的情況下修改內部值,與Cell<T>
和RefCell<T>
不同,UnsafeCell<T>
不提供任何運行時檢查來確保安全性,因此,使用UnsafeCell<T>
可能會導致未定義行為,
此外,Rust還提供了一種弱參考型別Weak<T>
,它可以與Rc<T>
或Arc<T>
一起使用來創建回圈參考,Weak<T>
不會增加參考計數,因此它不會阻止值被釋放,
Box<T>
Box<T>
是最簡單的智能指標,它允許你在堆上分配值并在離開作用域時自動釋放記憶體,
Box<T>
通常用于以下情況:
- 當你有一個型別,但不確定它的大小時,可以使用
Box<T>
來在堆上分配記憶體,例如,遞回型別通常需要使用Box<T>
來分配記憶體, - 當你有一個大型資料結構并希望在堆疊上分配記憶體時,可以使用
Box<T>
來在堆上分配記憶體,這樣可以避免堆疊溢位的問題, - 當你希望擁有一個值并只關心它的型別而不是所占用的記憶體時,可以使用
Box<T>
,例如,當你需要將一個閉包傳遞給函式時,可以使用Box<T>
來存盤閉包,
總之,當你需要在堆上分配記憶體并管理其生命周期時,可以考慮使用Box<T>
,
下面是一個簡單的例子:
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
復制代碼
這里定義了變數 b,其值是一個指向被分配在堆上的值 5 的 Box,這個程式會列印出 b = 5;在這個例子 中,我們可以像資料是儲存在堆疊上的那樣訪問 box 中的資料,正如任何擁有資料所有權的值那樣,當像 b 這樣的 box 在 main 的末尾離開作用域時,它將被釋放,這個釋放程序作用于 box 本身(位于堆疊上) 和它所指向的資料(位于堆上),
但是Box<T>
無法同時在多個地方對同一個值進行參考
enum List {
Cons(i32, Box),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
復制代碼
編譯會得出如下錯誤: error[E0382]: use of moved value: a
,因為b和c無法同時擁有a的所有權,這個時候我們要用Rc<T>
Rc<T>
Reference Counted 參考計數
Rc<T>
是一個參考計數型別,它允許多個指標指向同一個值,當最后一個指標離開作用域時,值將被釋放,Rc<T>
不是執行緒安全的,因此不能在多執行緒環境中使用,
Rc<T>
通常用于以下情況:
- 當你希望在多個地方共享資料時,可以使用
Rc<T>
,解決了使用Box<T>
共享資料時出現編譯錯誤 - 當你希望創建一個回圈參考時,可以使用
Rc<T>
和Weak<T>
來實作,
下面是一個簡單的例子,演示如何使用Rc<T>
來共享資料:
use std::rc::Rc;
fn main() {
let data = https://www.cnblogs.com/liujin-now/archive/2023/04/20/Rc::new(vec![1, 2, 3]);
let data1 = data.clone();
let data2 = data.clone();
println!("data: {:?}", data);
println!("data1: {:?}", data1);
println!("data2: {:?}", data2);
}
復制代碼
這個例子中,我們使用Rc::new
來創建一個新的Rc<T>
實體,然后,我們使用clone
方法來創建兩個新的指標,它們都指向同一個值,由于Rc<T>
實作了參考計數,所以當最后一個指標離開作用域時,值將被釋放,
但是Rc<T>
在多執行緒中容易引發執行緒安全問題,為了解決這個問題,又有了Arc<T>
Arc<T>
Atomically Reference Counted 原子參考計數
Arc<T>
是一個執行緒安全的參考計數型別,它允許多個指標在多個執行緒之間共享同一個值,當最后一個指標離開作用域時,值將被釋放,
Arc<T>
通常用于以下情況:
- 當你希望在多個執行緒之間共享資料時,可以使用
Arc<T>
,是Rc<T>
的多執行緒版本, - 當你希望在執行緒之間傳遞資料時,可以使用
Arc<T>
來實作,
下面是一個簡單的例子,演示如何使用Arc<T>
來在執行緒之間共享資料:
use std::sync::Arc;
use std::thread;
fn main() {
let data = https://www.cnblogs.com/liujin-now/archive/2023/04/20/Arc::new(vec![1, 2, 3]);
let data1 = data.clone();
let data2 = data.clone();
let handle1 = thread::spawn(move || {
println!("data1: {:?}", data1);
});
let handle2 = thread::spawn(move || {
println!("data2: {:?}", data2);
});
handle1.join().unwrap();
handle2.join().unwrap();
}
復制代碼
這個例子中,我們使用Arc::new
來創建一個新的Arc<T>
實體,然后,我們使用clone
方法來創建兩個新的指標,它們都指向同一個值,接著,我們在執行緒中使用這些指標來訪問共享資料,由于Arc<T>
實作了執行緒安全的參考計數,所以當最后一個指標離開作用域時,值將被釋放,
Weak<T>
弱參考型別
Weak<T>
是一個弱參考型別,它可以與Rc<T>
或Arc<T>
一起使用來創建回圈參考,Weak<T>
不會增加參考計數,因此它不會阻止值被釋放,
當你希望創建一個回圈參考時,可以使用Rc<T>
或Arc<T>
和Weak<T>
來實作,
Weak<T>
通常用于以下情況:
-
當你希望觀察一個值而不擁有它時,可以使用
Weak<T>
來實作,由于Weak<T>
不會增加參考計數,所以它不會影響值的生命周期,
下面是一個簡單的例子,演示如何使用Rc<T>
和Weak<T>
來創建一個回圈參考:
use std::rc::{Rc, Weak};
struct Node {
value: i32,
next: Option<Rc<Node>>,
prev: Option<Weak<Node>>,
}
fn main() {
let first = Rc::new(Node { value: 1, next: None, prev: None });
let second = Rc::new(Node { value: 2, next: None, prev: Some(Rc::downgrade(&first)) });
first.next = Some(second.clone());
}
復制代碼
這個例子中,我們定義了一個Node
結構體,它包含一個值、一個指向下一個節點的指標和一個指向前一個節點的弱參考,然后,我們創建了兩個節點first
和second
,并使用Rc::downgrade
方法來創建一個弱參考,最后,我們將兩個節點連接起來,形成一個回圈參考,
需要注意的是,由于Weak<T>
不會增加參考計數,所以它不會阻止值被釋放,當你需要訪問弱參考指向的值時,可以使用upgrade
方法來獲取一個臨時的強參考,如果值已經被釋放,則upgrade
方法會回傳None
,
UnsafeCell<T>
UnsafeCell<T>
是一個底層的內部可變性型別,它允許你在不可變參考的情況下修改內部值,與Cell<T>
和RefCell<T>
不同,UnsafeCell<T>
不提供任何運行時檢查來確保安全性,因此,使用UnsafeCell<T>
可能會導致未定義行為,
由于UnsafeCell<T>
是一個底層型別,它通常不直接用于應用程式代碼,相反,它被用作其他內部可變性型別(如Cell<T>
和RefCell<T>
)的基礎,
下面是一個簡單的例子,演示如何使用UnsafeCell<T>
來修改內部值:
use std::cell::UnsafeCell;
fn main() {
let x = UnsafeCell::new(1);
let y = &x;
let z = &x;
unsafe {
*x.get() = 2;
*y.get() = 3;
*z.get() = 4;
}
println!("x: {}", unsafe { *x.get() });
}
復制代碼
這個例子中,我們使用UnsafeCell::new
來創建一個新的UnsafeCell<T>
實體,然后,我們創建了兩個不可變參考y
和z
,它們都指向同一個值,接著,在一個unsafe
塊中,我們使用get
方法來獲取一個裸指標,并使用它來修改內部值,由于UnsafeCell<T>
實作了內部可變性,所以我們可以在不可變參考的情況下修改內部值,
需要注意的是,由于UnsafeCell<T>
不提供任何運行時檢查來確保安全性,所以使用它可能會導致未定義行為,因此,在大多數情況下,你應該使用其他內部可變性型別(如Cell<T>
和RefCell<T>
),而不是直接使用UnsafeCell<T>
,
Cell<T>
Cell<T>
是一個內部可變性型別,它允許你在不可變參考的情況下修改內部值,Cell<T>
只能用于Copy
型別,因為它通過復制值來實作內部可變性,
Cell<T>
通常用于以下情況:
- 當你需要在不可變參考的情況下修改內部值時,可以使用
Cell<T>
來實作內部可變性, - 當你需要在結構體中包含一個可變欄位時,可以使用
Cell<T>
來實作, 下面是一個簡單的例子,演示如何使用Cell<T>
來修改內部值:
use std::cell::Cell;
fn main() {
let x = Cell::new(1);
let y = &x;
let z = &x;
x.set(2);
y.set(3);
z.set(4);
println!("x: {}", x.get());
}
復制代碼
這個例子中,我們使用Cell::new
來創建一個新的Cell<T>
實體,然后,我們創建了兩個不可變參考y
和z
,它們都指向同一個值,接著,我們使用set
方法來修改內部值,由于Cell<T>
實作了內部可變性,所以我們可以在不可變參考的情況下修改內部值,
需要注意的是,由于Cell<T>
通過復制值來實作內部可變性,所以它只能用于Copy
型別,如果你需要在不可變參考的情況下修改非Copy
型別的值,可以考慮使用RefCell<T>
,
RefCell<T>
RefCell<T>
是一個內部可變性型別,它允許你在不可變參考的情況下修改內部值,與Cell<T>
不同,RefCell<T>
可以用于非Copy
型別,
RefCell<T>
通過借用檢查來確保運行時的安全性,當你嘗試獲取一個可變參考時,RefCell<T>
會檢查是否已經有其他可變參考或不可變參考,如果有,則會發生運行時錯誤,
RefCell<T>
通常用于以下情況:
-
當你需要在不可變參考的情況下修改內部值時,可以使用
RefCell<T>
來實作內部可變性, -
當你需要在結構體中包含一個可變欄位時,可以使用
RefCell<T>
來實作,
下面是一個簡單的例子,演示如何使用RefCell<T>
來修改內部值:
use std::cell::RefCell;
fn main() {
let x = RefCell::new(vec![1, 2, 3]);
let y = &x;
let z = &x;
x.borrow_mut().push(4);
y.borrow_mut().push(5);
z.borrow_mut().push(6);
println!("x: {:?}", x.borrow());
}
復制代碼
這個例子中,我們使用RefCell::new
來創建一個新的RefCell<T>
實體,然后,我們創建了兩個不可變參考y
和z
,它們都指向同一個值,接著,我們使用borrow_mut
方法來獲取一個可變參考,并使用它來修改內部值,由于RefCell<T>
實作了內部可變性,所以我們可以在不可變參考的情況下修改內部值,
需要注意的是,由于RefCell<T>
通過借用檢查來確保運行時的安全性,所以當你嘗試獲取一個可變參考時,如果已經有其他可變參考或不可變參考,則會發生運行時錯誤,from劉金,轉載請注明原文鏈接,感謝!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550541.html
標籤:其他
上一篇:Java的值傳遞和參考傳遞
下一篇:返回列表