A Note of Black Hat Rust— Smart Pointers & Lifetime Annotation

Yen
Rustaceans
Published in
6 min readJul 9, 2024

✏️ 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

Index

  • Smart Pointers
  • Lifetime Annotation

👩‍💻This note is still within the scope of Chapter 1 of the book.

Smart Pointers

  1. It prevents common memory safety issues by enforcing strict ownership and borrowing rules.
  2. It ensures resources are properly cleaned up when they are no longer needed.
  3. 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);
}
The result of example #0

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 result of example #1

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());
}
The result of example #2

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();
}
The result of example #3

Example #4 — Arc<T>

// 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());
}
The result of example #4

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
}
The result of example #5

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}"); // |
} // ---------+
The incrooect result of example #6

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}"); // | |
// --+ |
} // ----------+
The correct result of example #6

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}");
}
}
The result of example #7

Conclusion

The author of “Black Hat Rust” suggested we should try NOT to use lifetimes annotation and macros.

--

--

Yen
Rustaceans

Programming is a mindset. Cybersecurity is the process. Focus on Python & Rust currently. More about me | https://msha.ke/monles