返回开发者新闻

Rust Nibbles - Gazebo : Dupe

2021年7月6日发布者:Navyata Bawa

This article was written in collaboration with Neil Mitchell, a Software Engineer in the Developer Infrastructure organization at Facebook.

The Rust library Gazebo contains a collection of well-tested Rust utilities in the form of standalone modules. In this series of blog posts, we will cover some of the modules that make up the Gazebo library. In today’s blog, we will cover the Dupe trait. This blog is a part of our Rust Nibbles series, where we go over the various Rust libraries we have open-sourced to learn more about what motivated their creation and how one can use them.

In Rust, there are two related traits for “duplicating” a value - Copy and Clone. In Gazebo we’ve introduced a third such trait, we call Dupe, which is available from the Gazebo Prelude. Let’s go through these three traits, why we think there is value in a third such trait, and how we use it in practice.

The Copy trait represents values which can be duplicated by simply copying their bit pattern. It’s so cheap that in most cases the Rust compiler will automatically copy the values for you. In contrast, the Clone trait represents values which call a custom clone() method to duplicate themselves. As two examples of things that implement the Clone trait:

  • The Rust reference-counted type Rc (and similar Arc, the atomic reference count) both support Clone, and their clone implementation bumps a reference count.
  • The type Vec and String both support Clone, which allocate memory and copy the contents across, meaning that a type such as Vec<String> will typically do 1 allocation for the vector, and 1 for each string it contains.

While the Copy trait is quite well constrained, the Clone trait covers a massive variety of performance characteristics. In most cases, if you see yourself cloning a Vec<String> a lot, there’s a good chance you should put it in an Arc and make the cloning vastly cheaper. The problem comes when reading the code. At code review, we might see:

let xs = ys.clone();
        

That might allocate 2GB of memory. Or it might increment a counter. Figuring out which requires a lot of context. One possible solution is to explicitly use Arc::clone, e.g.:

let xs = Arc::clone(ys);
        

This pattern has the advantage of making the cost clear, but the disadvantage is that as soon as you wrap a type inside a struct, you can no longer call Arc::clone on it, even though the cost of cloning remains the same. This pattern breaks abstraction.

Our solution is to introduce the Dupe trait. It provides a dupe method that just forwards to clone, but we only define Dupe in cases where the cost of copying is “trivial”. In practice, that means zero allocation, and not too many reference count increments. Alas, the definition is a little imprecise, with no definition of “too many”, but it has served us well. Now, for types that implement Dupe, we see:

let xs = ys.dupe();
        

The dupe function is a clear sign during code review that we don’t need to worry. Having significantly reduced the number of calls to clone, we can pay much more attention to them. The code review experience has improved, letting us focus on the right things.

Gazebo also provides a Dupe derive macro, so you can write:

use gazebo::prelude::*;
#[derive(Clone, Dupe)]
struct MyArc(Arc<String>);

        

We think of the Dupe trait as part of the standard package of things that should always be available, so we included it in the gazebo::prelude module, but it can also be found standalone at gazebo::dupe::Dupe, for those who don’t want to use the rest of the Gazebo Prelude.

We hope that this blog helps you understand the Dupe trait, how to use it and gives you good insight into what it does. Look out for our next blog in this series, where we discuss the Variants module, which provides tools for working with the different variants of an enum.

Be sure to check out our previous blog in the Gazebo series to learn more about the various features the Gazebo library has to offer -
Gazebo - Prelude

About the Rust Nibbles series

We at Facebook believe that Rust is an outstanding language that shines in critical issues such as memory safety, performance and reliability. We joined the Rust Foundation to help contribute towards the growth, advancement and adoption of Rust, and towards sustainable development of open source technologies and developer communities across the world.

This blog is a part of our Rust Nibbles series, where we go over the various Rust libraries we have open-sourced to learn more about what motivated their creation and how one can use them. We hope that this series helps you create amazing projects by using these libraries and encourages you to try them out.

To learn more about Facebook Open Source, visit our open source site, subscribe to our YouTube channel, or follow us on Twitter and Facebook.