Smart pointers
A smart pointer is a data structure that not only acts like a pointer but provides additional functionality. This “smartness” comes from the fact that smart pointers encapsulate additional logical or semantic rules, which are automatically applied to simplify memory or resource management tasks.
While different programming languages implement smart pointers in various ways, they all share a common goal: to manage the life cycle of the resources they point to.
Here are some key features of smart pointers:
-
Ownership: Smart pointers keep track of the memory they point to, and can automatically reclaim (or “free”) that memory when it’s no longer needed. This can greatly simplify memory management, as the programmer doesn’t need to manually release memory, reducing the risk of memory leaks.
-
Reference Counting: Some smart pointers, like the
Rc<T>
in Rust orshared_ptr
in C++, keep a count of the number of references (i.e., pointers) to an object. This object is only deallocated when there are no more references to it. -
Dereferencing: Like regular pointers, smart pointers implement the dereferencing operation, allowing access to the data they point to.
-
Polymorphism: In object-oriented languages, smart pointers can be used to enable polymorphism when pointing to base and derived class objects.
-
Thread Safety: Some smart pointers, like
Arc<T>
in Rust orstd::atomic_shared_ptr
in C++20, are thread-safe, meaning they can be safely used in concurrent programming.
In Rust, smart pointers are essential components due to the language’s focus on safety and zero-cost abstractions. Box<T>
, Rc<T>
, and RefCell<T>
are some of the commonly used smart pointers in Rust.
-
Box<T>
: This is the simplest kind of smart pointer. It allows you to put data on the heap rather than the stack.Box<T>
is often used in Rust in two scenarios: when you have a type of known size but need to store a value of a type that might have an unknown size at compile time, or when you have a large data item and want to transfer ownership to a function without copying the data to prevent stack overflow. -
Rc<T>
: “Rc” stands for reference counting. TheRc<T>
smart pointer allows your program to have multiple read-only references to the same data item on the heap. It keeps track of the number of references to the data on the heap, and once the reference count goes to zero, meaning there are no more references to the data, it cleans up the data. -
RefCell<T>
: Rust performs borrow checking at compile time which ensures that references to data obey the rules of either one write or multiple reads. However, sometimes you may need to do such checking at runtime.RefCell<T>
provides this functionality. This means that while you can borrow mutable references at runtime, your program will panic if you violate the rules of one write or multiple reads.
These are some of the basic smart pointers in Rust. There are several other smart pointers, like Arc<T>
, Mutex<T>
, etc., which are all designed to deal with ownership and concurrency issues in Rust.
It’s worth noting that while these types are called “smart pointers”, they’re not limited to mimicking pointer-like behavior. They’re actually more general constructs often used to add structure to a value, such as tracking reference counts, thread-safe access, and more.
Box< T >
Box<T>
is a very important smart pointer in Rust. It allows you to store data on the heap rather than the stack. This is mainly used in Rust for the following two scenarios:
-
Moving data to the heap: By default, Rust stores data on the stack. However, stack space is limited and the data on the stack is immediately removed when it goes out of scope. If you need to store a large amount of data in memory or need to pass data between scopes without losing ownership, you can use
Box<T>
to move the data to the heap. -
Storing dynamically sized types: In Rust, the size of all types must be known at compile time. However, there might be cases where you want to store a type whose size is not known at compile time.
Box<T>
can be used in this case, as the size ofBox<T>
itself is known regardless of the size of the data on the heap it points to.
When using Box<T>
, Rust will automatically clean up the data on the heap when the Box<T>
goes out of scope, meaning you do not need to manually manage memory.
Here is a simple example of Box<T>
:
fn main() {
let b = Box::new(5); // b is a pointer to a box in the heap
println!("b = {}", b);
}
In this example, the integer 5 is stored on the heap, rather than on the stack. The variable b
is a pointer to the value stored on the heap. When b
goes out of scope, Rust’s automatic memory management system cleans up the data on the heap, so you do not need to manually free the memory.
use std::ops::Deref;
fn main() {
/*
如果一個(gè)結(jié)構(gòu)體實(shí)現(xiàn)了deref和drop的Trait,那他們就不是普通結(jié)構(gòu)體了。
Rust提供了堆上存儲(chǔ)數(shù)據(jù)的能力并把這個(gè)能力封裝到了Box中。
把棧上的數(shù)據(jù)搬到堆上的能力,就叫做裝箱。
Box可以把數(shù)據(jù)存儲(chǔ)到堆上,而不是棧上,box 裝箱,棧還是包含指向堆上數(shù)據(jù)的指針。
*/
let a = 6;
let b = Box::new(a);
println!("b = {}", b); // b = 6
let price1 = 158;
let price2 = Box::new(price1);
println!("{}", 158 == price1); // true
println!("{}", 158 == *price2); // true
let x = 666;
let y = CustomBox::new(x);
println!("666 == x is {}", 666 == x); // 666 == x is true
println!("666 == *y is {}", 666 == *y); // 666 == *y is true
println!("x == *y is {}", x == *y); // x == *y is true
}
struct CustomBox<T> {
value: T
}
impl <T> CustomBox<T> {
fn new(v: T) -> CustomBox<T> {
CustomBox { value: v }
}
}
impl <T> Deref for CustomBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl <T> Drop for CustomBox<T> {
fn drop(&mut self) {
println!("drop CustomBox 對(duì)象!")
}
}
Rc < T >
Rc<T>
stands for ‘Reference Counting’. It’s a smart pointer that allows you to have multiple owners of the same data. When each owner goes out of scope, the reference count is decreased. When the count reaches zero, the data is cleaned up.
Here’s a simple example:
use std::rc::Rc;
fn main() {
let data = Rc::new("Hello, World!"); // data now has a reference count of 1
{
let _clone = Rc::clone(&data); // data now has a reference count of 2
println!("Inside the scope, data is: {}", *_clone);
} // _clone goes out of scope and data's reference count decreases to 1
println!("Outside the scope, data is: {}", *data);
} // data goes out of scope and its reference count decreases to 0, the data is cleaned up
RefCell < T >
RefCell<T>
provides ‘interior mutability’, a design pattern in Rust that allows you to mutate data even when there are immutable references to that data. It enforces the borrowing rules at runtime instead of compile time.
Here’s a simple example:
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
{
let mut mutable_reference = data.borrow_mut();
*mutable_reference += 1;
println!("Inside the scope, data is: {}", *mutable_reference);
} // mutable_reference goes out of scope, the lock is released
println!("Outside the scope, data is: {}", *data.borrow());
}
In this example, you can see that we borrowed a mutable reference to the data inside RefCell
using borrow_mut
, modified it, and then the lock was automatically released when the mutable reference went out of scope. This demonstrates the dynamic checking that RefCell<T>
provides.
Note1: The borrow_mut()
function is a method of RefCell<T>
in Rust. This function is used to gain mutable access to the underlying value that the RefCell
wraps around.
The RefCell
struct in Rust enforces the borrow rules at runtime, allowing you to have multiple immutable references or one mutable reference at any point in time. If you attempt to violate these rules, your program will panic at runtime.
When you call borrow_mut()
on a RefCell
, you get a RefMut
smart pointer that allows mutable access to the underlying data. Once this RefMut
is dropped (which happens automatically when it goes out of scope), others can then borrow the RefCell
again.
Here’s an example:
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
{
let mut mutable_reference = data.borrow_mut();
*mutable_reference += 1;
println!("Inside the scope, data is: {}", *mutable_reference);
} // mutable_reference goes out of scope, the lock is released
println!("Outside the scope, data is: {}", *data.borrow());
}
In this example, mutable_reference
is a mutable reference to the integer wrapped in data
, which is a RefCell
. We increment the integer by 1, and after mutable_reference
goes out of scope, we can borrow data
again.
Note2: The borrow()
function is a method of RefCell<T>
in Rust. This function is used to gain immutable access to the underlying value that the RefCell
wraps around.
Similar to borrow_mut()
, it provides a way to access the data inside the RefCell
. The difference is that borrow()
gives you an immutable reference (Ref<T>
), while borrow_mut()
gives you a mutable reference (RefMut<T>
).
The RefCell
checks at runtime to make sure the borrowing rules are not violated, which are:
- You may have either one mutable reference or any number of immutable references at the same time, but not both.
- References must always be valid.
If you try to call borrow()
when the value is already mutably borrowed (through borrow_mut()
), or try to call borrow_mut()
when the value is immutably borrowed (through borrow()
), your program will panic at runtime.
Here’s how you might use borrow()
:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-620380.html
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
{
let mutable_reference = data.borrow_mut();
*mutable_reference += 1;
} // mutable_reference goes out of scope, the lock is released
let immutable_reference = data.borrow();
println!("Data is: {}", *immutable_reference);
}
In this example, we first use borrow_mut()
to mutate the value inside data
, then we use borrow()
to get an immutable reference to the data.文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-620380.html
到了這里,關(guān)于Rust- 智能指針的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!