Understanding Guard and Deref in Rust

Guard

<span>Guard</span> is a common concept in Rust, meaning “to guard”. What does it guard? It guards resources, data, etc., within a specific scope.

For example, the guards for mutexes and read-write locks are:

  • <span>MutexGuard</span>
  • <span>RwLockReadGuard</span> and <span>RwLockWriteGuard</span>

They are an implementation of RAII (Resource Acquisition Is Initialization), a resource management technique from C++ that is also widely adopted in Rust, which can be understood as a way of managing local resources.

These resources are associated with local variables, and when the local variables are released, the resources are also released. For example:

let mutex = Mutex::new(String::from("hello"));
let mutex_guard = mutex.lock().unwrap();

<span>mutex.lock()</span> returns a <span>MutexGuard</span>, and when <span>mutex_guard</span> goes out of scope, the resource locked by <span>mutex</span> will be released. This perfectly integrates with Rust’s lifetimes, so in Rust, it is usually unnecessary to manually release locks; they are automatically released when the scope of the lock ends.

The above is an example of <span>Guard</span> “guarding” locked resources. <span>Guard</span> can also “guard” data.

For example, in Rocket, to obtain JSON data, it is common to use <span>Json<T></span> as a route parameter.

#[post("/hello", data = "&lt;req&gt;")]
fn hello(req: Json<SomeJson>) {}

Here, <span>Json<SomeJson></span> is a guard that contains an associated type, and when it goes out of scope, the associated data will be released.

From the above examples, we can see that guards have the following characteristics:

  • • Local scope
  • • Automatic release

Therefore, we can understand guards as follows:

  • • A guard can be seen as a design pattern used to protect and manage resources within a specific scope.
  • • In Rust, guards are typically types that implement the Drop trait, ensuring that resources are properly cleaned up when the object goes out of scope.

Deref

When using <span>Mutex</span>, you may encounter the following scenario:

let mutex = Mutex::new(vec![1, 2, 3]);
let mutex_guard = mutex.lock().unwrap();
let len: usize = mutex_guard.len();

Here, the type of <span>mutex_guard</span> is <span>MutexGuard<Vec<i32>></span>, but you can directly use the <span>len</span> method from Vec. This is mainly due to the contribution of <span>Deref</span>.

<span>Deref</span> is essentially dereferencing. Most <span>Guard</span>s implement <span>Deref</span> because a guard is just a wrapper around a resource, needing to access its internal resource in a simple and safe manner.

In the above example, it is more accurately described as “automatic dereferencing” because there is no manual call to the <span>deref</span> method or use of the <span>*</span> operator. Automatic dereferencing is handled by the compiler.

The compiler automatically dereferences in the following situations:

  • • When calling a function. For example, the above <span>mutex_guard.len()</span><code><span>.</span>
  • • When accessing fields. For example:
  •   struct MyStruct {
          value: i32,
      }
      let mutex = Mutex::new(MyStruct{value: 1});
      let mutex_guard = mutex.lock().unwrap();
      // Automatic dereferencing, can directly access field value
      let val = mutex_guard.value;
  • • When passing function parameters. For example, when <span>&str</span> is used as a function parameter, it can accept both <span>String</span> and <span>&str</span>.

In fact, <span>Deref</span> is very suitable for the wrapper pattern because the automatic dereferencing mechanism allows for easy access to the original object, and automatic dereferencing is handled at compile time with no runtime overhead, making it convenient to use during development.

Leave a Comment