Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Rust Standard Library Cookbook

You're reading from   Rust Standard Library Cookbook Over 75 recipes to leverage the power of Rust

Arrow left icon
Product type Paperback
Published in Mar 2018
Publisher Packt
ISBN-13 9781788623926
Length 360 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Jan Hohenheim Jan Hohenheim
Author Profile Icon Jan Hohenheim
Jan Hohenheim
Daniel Durante Daniel Durante
Author Profile Icon Daniel Durante
Daniel Durante
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Title Page
Copyright and Credits
Packt Upsell
Contributors
Preface
1. Learning the Basics 2. Working with Collections FREE CHAPTER 3. Handling Files and the Filesystem 4. Serialization 5. Advanced Data Structures 6. Handling Errors 7. Parallelism and Rayon 8. Working with Futures 9. Networking 10. Using Experimental Nightly Features 1. Other Books You May Enjoy Index

Using the constructor pattern


You may have asked yourself how to idiomatically initialize complex structs in Rust, considering it doesn't have constructors. The answer is simple, there is a constructor, it's just a convention rather than a rule. Rust's standard library uses this pattern very often, so we need to understand it if we want to use the std effectively.

Getting ready

In this recipe, we are going to talk about how a user interacts with a struct. When we say user in this context, we don't mean the end user that clicks on the GUI of the app you're writing. We're referring to the programmer that instantiates and manipulates the struct.

How to do it...

  1. In the src/bin folder, create a file called constructor.rs

  2. Add the following code and run it with cargo run --bin constructor:

1  fn main() {
2    // We don't need to care about
3    // the internal structure of NameLength
4    // Instead, we can just call it's constructor
5    let name_length = NameLength::new("John");
6 
7    // Prints "The name 'John' is '4' characters long"
8    name_length.print();
9  }
10 
11  struct NameLength {
12    name: String,
13    length: usize,
14  }
15 
16  impl NameLength {
17    // The user doesn't need to setup length
18    // We do it for him!
19    fn new(name: &str) -> Self {
20      NameLength {
21        length: name.len(),
22        name,
23      }
24    }
25 
26    fn print(&self) {
27      println!(
28        "The name '{}' is '{}' characters long",
29          self.name,
30          self.length
31      );
32    }
33  }

How it works...

If a struct provides a method called new that returns Self, the user of the struct will not configure or depend upon the members of the struct, as they are considered to be in an internal hidden state.

In other words, if you see a struct that has a new function, always use it to create the structure. This has the nice effect of enabling you to change as many members of the struct as you want without the user noticing anything, as they are not supposed to look at them anyway.

The other reason to use this pattern is to guide the user to the correct way of instantiating a struct. If one has nothing but a big list of members that have to be filled with values, one might feel a bit lost. If one, however, has a method with only a few self-documenting parameters, it feels way more inviting.

There's more...

You might have noticed that for our example we really didn't need a length member and could have just calculated a length whenever we print. We use this pattern anyway, to illustrate the point of its usefulness in hiding implementations. Another good use for it is when the members of a struct themselves have their own constructors and one needs to cascade the constructor calls. This happens, for example, when we have a Vec as a member, as we will see later in the book, in the, Using a vector section in Chapter 2, Working with Collections.

Sometimes, your structs might need more than one way to initialize themselves. When this happens, try to still provide a new() method as your default way of construction and name the other options according to how they differ from the default. A good example of this is again vector, which not only provides a Vec::new() constructor but also a Vec::with_capacity(10), which initializes it with enough space for 10 items. More on that again in the Using a vector section in Chapter 2, Working with Collections.

When accepting a kind of string (either &str, that is, a borrowed string slice, or String, that is, an owned string) with plans to store it in your struct, like we do in our example, also considering a Cow. No, not the big milk animal friends. A Cow in Rust is a Clone On Write wrapper around a type, which means that it will try to borrow a type for as long as possible and only make an owned clone of the data when absolutely necessary, which happens at the first mutation. The practical effect of this is that, if we rewrote our NameLength struct in the following way, it would not care whether the called passed a &str or a String to it, and would instead try to work in the most efficient way possible:

use std::borrow::Cow;
struct NameLength<'a> {
    name: Cow<'a, str>,
    length: usize,
}

impl<'a> NameLength<'a> {
    // The user doesn't need to setup length
    // We do it for him!
    fn new<S>(name: S) -> Self
    where
        S: Into<Cow<'a, str>>,
    {
        let name: Cow<'a, str> = name.into();
        NameLength {
            length: name.len(),
            name,
        }
    }

    fn print(&self) {
        println!(
            "The name '{}' is '{}' characters long",
            self.name, self.length
        );
    }
}

If you want to read more about Cow, check out this easy-to-understand blog post by Joe Wilm: https://jwilm.io/blog/from-str-to-cow/.

The Into trait used in the Cow code is going to be explained in the Converting types into each other section in Chapter 5, Advanced Data Structures.

See also

  • Using a vector recipe inChapter 2, Working with Collections
  • Converting types into each other recipe inChapter 5, Advanced Data Structures
lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at £13.99/month. Cancel anytime
Visually different images