A Note of Black Hat Rust— Smart Pointers & Lifetime Annotation
✏️ This note was taken while learning Black Hat Rust by Sylvain Kerkour and researching Rust documentation. 📖 This book is fantastic, not just for passionate hobbyists, but also for professionals looking to dive deeper into Rust and cybersecurity. 🔒
📒 I use Replit to run the code, and I suggest giving it a try. 🔧 Pair it with the book and the examples in this blog post for an enhanced learning experience! 🔑
Resources
- GitHub | Black Hat Rust
- Blogs & books | Black Hat Rust
Index
- Smart Pointers
- Lifetime Annotation
👩💻This note is still within the scope of Chapter 1 of the book.
Smart Pointers
- It prevents common memory safety issues by enforcing strict ownership and borrowing rules.
- It ensures resources are properly cleaned up when they are no longer needed.
- If also offers thread-safe mechanisms for sharing data between threads.
Example #0 — Box<T>
Box<T>
for allocating values on the heap (Rust Documentation, 2024).- It provides heap allocation for values. It is used when you need to store data on the heap rather than the stack.
- It ‘s useful for recursive data structures and when you need to enforce size constraints.
// This code is from Rust Documentation
// https://doc.rust-lang.org/book/ch15-01-box.html
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
Example #1 — Rc<T> (Reference Counting)
- In the book “Black Hat Rust”, the author suggested smart pointers provide an effective solution for managing long-lived references, whether they are shared or exclusive, and whether they are mutable or immutable.
- Also, you can find the explanation in the Rust Documentation.
// This code is from the book "Black Hat Rust" by Sylvain Kerkour
use std::rc::Rc;
fn main() {
let pointer = Rc::new(1);
{
let second_pointer = pointer.clone(); // or Rc::clone(&pointer)
println!("{}", *second_pointer);
}
println!("{}", *pointer);
}
The output of
example #1
has corrected (thanks, Pm).
Example #2— Rc<T> (Reference Counting)
Rc<T>
, a reference counting type that enables multiple ownership (Rust Documentation, 2024).- It keeps track of the number of references to the data.
- Suitable for single-threaded scenarios where multiple parts of the program need to read from the same data.
- This example is to obtain a mutable, shared pointer (Sylvain Kerkour, 2022).
- The code in this example presents “The internal mutable pattern”.
- ⛔ With
RefCell<T>
, if you break these rules, your program will panic and exit (Rust Documentation, 2024).
// This code is from the book "Black Hat Rust" by Sylvain Kerkour
use std::cell::{RefCell, RefMut};
use std::rc::Rc;
fn main() {
let shared_string = Rc::new(RefCell::new("Hello".to_string()));
{
let mut hello_world: RefMut<String> = shared_string.borrow_mut();
hello_world.push_str(" World");
}
println!("{}", shared_string.take());
}
Example #3 — Arc<T> (Atomic Reference Counting)
- It is similar to
Rc<T>
, but safe to use in multi-threaded contexts (A.K.A. concurrent situations) due to atomic reference counting. - It’s used when data needs to be shared across multiple threads.
- More details in Rust Documentation.
// This code is from Rust Documentation
// https://doc.rust-lang.org/book/ch16-03-shared-state.html?highlight=arc#atomic-reference-counting-with-arct
use std::sync::Arc;
use std::thread;
fn main() {
let a = Arc::new(5);
let b = Arc::clone(&a);
let handle = thread::spawn(move || {
println!("b = {}", b);
});
println!("a = {}", a);
handle.join().unwrap();
}
Example #4 — Arc<T>
- Another example in Rust Documentation (2024).
// This code is from Rust Documentation
// https://doc.rust-lang.org/book/ch16-03-shared-state.html?highlight=arc#atomic-reference-counting-with-arct
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
Example #5 — Arc<Mutex<T>>
- For mutable shared variables (Sylvain Kerkour, 2022).
- I will discuss a bit more about this in Note #6.
// This code is from the book "Black Hat Rust" by Sylvain Kerkour
use std::sync::{Arc, Mutex};
use std::{thread, time};
fn main() {
let pointer = Arc::new(Mutex::new(5));
let second_pointer = pointer.clone(); // or Arc::clone(&pointer)
thread::spawn(move || {
let mut mutable_pointer = second_pointer.lock().unwrap();
*mutable_pointer = 1;
});
thread::sleep(time::Duration::from_secs(1));
let one = pointer.lock().unwrap();
println!("{}", one); // 1
}
Lifetime Annotation
According to Rust Documentation (2024),
“Every reference in Rust has a lifetime, which is the scope for which that reference is valid.”
Example #6 — Avoid Dangling Reference
In this case, after the inner scope ends, the attempt to print the value in r
will fail. The reason that this code won’t compile is because what the value r
is referring to has gone out of scope before we try to use it (Rust Documentation, 2024).
// This code is from Rust Documention
// https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {r}"); // |
} // ---------+
This is the correct version.
// This code is from Rust Documention
//https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html
fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {r}"); // | |
// --+ |
} // ----------+
Example #7 — Lifetime Annotation
Lifetime annotations are specified using an apostrophe (i.e., 'a
) followed by a name. These annotations do not change the lifetimes themselves but tell the compiler how different references relate to each other (Rust Documentation, 2024).
Here are some examples:
- A reference to an
i32
without a lifetime parameter - a reference to an
i32
that has a lifetime parameter named'a
- a mutable reference to an
i32
that also has the lifetime'a
// This example is from Rust Documention
// https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html?highlight=lifetime%20annotation#lifetime-annotation-syntax
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
How it works:
// This example is from Rust Documention
// https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html?highlight=lifetime%20annotation#lifetime-annotation-syntax
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {result}");
}
}
Conclusion
The author of “Black Hat Rust” suggested we should try NOT to use lifetimes annotation and macros.