Rust Basics Review
"TLDR: This article systematically reviews the core mechanisms of the Rust language, including ownership transfer (implemented through assignment and `into`), the single-threaded shared `Rc` smart pointer, the cross-thread-safe `Arc` smart pointer, the `Mutex` for multi-threaded data protection, the borrowing helper functions `as_ref`/`as_deref` for safe reference conversion, as well as the error handling patterns of `Option`/`Result` and the rules for lifetime annotations. It focuses on analyzing the implementation principles and application scenarios of these features in memory management and concurrent programming."
Rust's syntax can be a bit overly complex, so sometimes it's worth doing a quick review.
Ownership Transfer
Assignment Operator =
let a = 6;
let b = a; // Ownership is transferred directly
into
This is syntactic sugar that works with the From trait. Its main purpose is ownership transfer + type conversion.
let a : String = "hello".to_string();
let t: Box<str> = s.into() // Transfers ownership and converts the type to Box<str>
Ownership Sharing
Although exclusive ownership is safe, there are many cases where multiple variables need to share ownership. This involves two different scenarios: multi-threaded and single-threaded.
Single-threaded Sharing Rc<T>
Multiple owners sharing one thing, essentially reference counting.
let a = Rc::new(5)
let b = Rc::clone(&a);
println!("{}", a + b);
Stack memory:
+-----+ Rc<T> (about 8 bytes)
| a | -----> ptr --------------------+
+-----+ |
v
Heap memory:
+---------------------------------------+
| RcBox<T>: |
| strong = 1 |
| weak = 0 |
| value = 42 |
+---------------------------------------+
Stack memory:
+-----+ Rc Rc
| a | -----> [ ptr ] ------+ [ ptr ] <----- | b |
+-----+ | +-----+
v
Heap memory (RcBox<T>):
+-------------------+
| strong = 2 | <- reference count +1
| weak = 0 |
| value = 42 |
+-------------------+
Multi-threaded Sharing Arc<T>
Similar to Rc, but thread-safe.
let a= Arc::new(5);
let b = Arc::clone(&a);
let t = thread::spawn(move || {
println!("{}", b);
})
t.join().unwrap();
Mutex Mutex<T>
When sharing data across threads, modifying the data requires locking.
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10:
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num +=1;
}));
for h in handles{
h.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
Borrowing Helpers: as_ref, as_deref
as_ref: borrow only, don't take ownership
let x: Option<String> = Some("hi".to_string());
let y: Option<&String> = x.as_ref(); // y is a borrow
as_deref: convert Option<Box> to Option<&T>, with automatic dereferencing
let x: Option<Box<String>> = Some(Box::new("hi".to_string()));
let y: Option<&str> = x.as_deref().map(|s| s.as_str());
Syntactic Sugar for Various Destructuring and Error Handling
Common Option Methods
let x: Option<i32> = Some(5);
x.is_some(); // True
x.is_none(); // False
x.unwrap(); // 5
x.unwrap_or(10) // 5, if None, returns the default value 10
x.map(|v| v* 2) // Some(10)
Common Result Methods
let r: Result<i32, &str> = Err("error");
r.is_ok() // false
r.is_err() // true
r.unwrap_or(10) // 10
r.map_err(|e| e.to_uppercase()) // Err("ERROR")
Lifetimes
Tells the compiler "how long references should live": when returning a reference, you must declare a lifetime, otherwise the compiler won't know how long the return value should live.
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() { s1 } else { s2 }
}
pub struct BangumiParser<'a> {
client: &'a BiliCLient
}
impl<'a> BangumiParser<'a> {
// ...
}
- Here,
<'a>represents the lifetime ofBangumiParser, and the lifetime of theclientreference is&'a. - This is a constraint: the lifetime of the
clientreference must be greater than or equal to the lifetime ofBangumiParser.