DEV Community

Alex
Alex

Posted on

 

Rust Concept Clarification: Deref vs AsRef vs Borrow vs Cow

Alt Text

By HanDong Zhang

Understanding by Module

In fact, the classification by standard library will first give you a small idea of what they do.

  1. std::ops::Deref . As you can see, Deref is categorized as an ops module. If you look at the documentation, you will see that this module defines the trait fr all the overloadable operators. For example, Add trait corresponds to +, while Deref trait corresponds to a shared(immutable) borrowing dereference operation, such as *v . Correspondingly, there is also the DerefMut trait, which corresponds to the dereferencing operation of exclusive(mutable) borrowing. Since the Rust ownership semantics is a language feature throughout , the semantics of Owner / immutable borrowing(&T)/ mutable borrowing(&mut T) all appear together.
  2. std::convert::AsRef . As you can see, AsRef is grouped under the convert module. if you look at the documentation, you will see that traits related to type conversions are defined in this module. For example, the familiar "From/To", "TryFrom/TryTo" and "AsRef/AsMut" also appear in pairs here, indicating that the feature is releated to type conversions. Based on the naming rules in the Rust API Guidelines , wen can infer that methods starting with as_ represent conversions from borrow -> borrow, i.e, reference -> reference , and are overhead-free. And such conversions do not fail.
  3. std::borrow::Borrow. As you can see, Borrow is categorized in the borrow module. The documentation for this module is very minimal, with a single sentence saying that this is for using borrowed data. So the trait is more or less related to expressing borrwo semantics. Three traits are provided: Borrow / BorrowMut/ ToOwned , which corresponds exactly to the ownership semantics.
  4. std::borrow::Cow. It can be seen that Cow is also classified as a borrow module. According to the description, Cow is a clone-on-write smart pointer. The main reason for putting it in the borrow module is to use borrowing as much as possible and avoid copying, as an optimization.

std::ops::Deref

First, let's look at the definition of the trait.

pub trait Deref {
    type Target: ?Sized;
    #[must_use]
    pub fn deref(&self) -> &Self::Target;
}
Enter fullscreen mode Exit fullscreen mode

The definition is not complicated, Deref contains only a deref method signature. The beauty of this trait is that it is called "implicitly" by the compiler, officially called "deref coercion ".

Here is an example from the standard library.

use std::ops::Deref;

struct DerefExample<T> {
    value: T
}

impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

let x = DerefExample { value: 'a' };
assert_eq!('a', *x);
Enter fullscreen mode Exit fullscreen mode

In the code, the DerefExample structure implements the Deref trait, so it can be executed using the dereference operator *. In the example, the value of the field value is returned directly.

As you can see, DerefExample has a pointer-like behavior , because it implements Deref, because it can be dereferenced. DerefExample also becomes a kind of smart pointer. This is one way to identify if a type is a smart pointer, by seeing if it implements Deref. But not all smart pointers implement Deref, some implent Drop, or both.

Now let's summarize Deref.

If T implements Deref<Target=U>, and x is an instance of type T, then.

  1. In an immutable context, the operation of *x (when T is neither a reference nor a primitive pointer) is equivalent to *Deref::deref(&x).
  2. The value of &T is forced to be converted to the value of &U. (deref coercion).
  3. T implements all the (immutable) methods of U.

The beauty of Deref is that it enhances the Rust development experience. A typical example from the standard library is that Vec<T> shares all the methods of slice by implemented Deref.

impl<T, A: Allocator> ops::Deref for Vec<T, A> {
    type Target = [