ePUB is an open, industry-standard format for eBooks. However, support of ePUB and its many features varies across reading devices and applications. Use your device or app settings to customize the presentation to your liking. Settings that you can customize often include font, font size, single or double column, landscape or portrait mode, and figures that you can click or tap to enlarge. For additional information about the settings and features on your reading device or app, visit the device manufacturer’s Web site.
Many titles include programming code or configuration examples. To optimize the presentation of these elements, view the eBook in singlecolumn, landscape mode and adjust the font size to the smallest setting. In addition to presenting code and configurations in the reflowable text format, we have included images of the code that mimic the presentation found in the print book; therefore, where the reflowable format may compromise the presentation of the code listing, you will see a “Click here to view code image” link. Click the link to view the print-fidelity code image. To return to the previous page viewed, click the Back button on your device or app.
Programming with Rust
Programming with Rust
Donis Marshall
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.
The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.
For information about buying this title in bulk quantities, or for special sales opportunities (which may include electronic versions; custom cover designs; and content particular to your business, training goals, marketing focus, or branding interests), please contact our corporate sales department at corpsales@pearsoned.com or (800) 382-3419.
For government sales inquiries, please contact governmentsales@pearsoned.com.
For questions about sales outside the U.S., please contact intlcs@pearson.com.
All rights reserved. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, request forms and the appropriate contacts within the Pearson Education Global Rights & Permissions Department, please visit www.pearson.com/permissions.
ISBN-13: 978-0-13-788965-5
ISBN-10: 0-13-788965-8
$PrintCode
Pearson’s Commitment to Diversity, Equity, and Inclusion
Pearson is dedicated to creating bias-free content that reflects the diversity of all learners. We embrace the many dimensions of diversity, including but not limited to race, ethnicity, gender, socioeconomic status, ability, age, sexual orientation, and religious or political beliefs.
Education is a powerful force for equity and change in our world. It has the potential to deliver opportunities that improve lives and enable economic mobility. As we work with authors to create content for every product and service, we acknowledge our responsibility to demonstrate inclusivity and incorporate diverse scholarship so that everyone can achieve their potential through learning. As the world’s leading learning company, we have a duty to help drive change and live up to our purpose to help more people create a better life for themselves and to create a better world.
Our ambition is to purposefully contribute to a world where:
Everyone has an equitable and lifelong opportunity to succeed through learning.
Our educational products and services are inclusive and represent the rich diversity of learners.
Our educational content accurately reflects the histories and experiences of the learners we serve.
Our educational content prompts deeper discussions with learners and motivates them to expand their own learning (and worldview).
While we work hard to present unbiased content, we want to hear from you about any concerns or needs with this Pearson product so that we can investigate and address them.
Please contact us with concerns about any potential bias at https://www.pearson.com/report-bias.html.
Contents
1 Introduction to Rust
Introduction
Functional Programming
Expression Oriented
Pattern-Oriented
Features
Safeness
Ownership
Lifetimes
Fearless Concurrency
Zero-Cost Abstraction
Rust Terminology
Tools
A Note About Security Summary
2 Getting Started
Preliminaries
Rust and Windows
Installing Rust
Advanced Rustup “Hello, World”
Compile and Run
Cargo
Library
Comments
Published Crates
Main Function
Command-Line Arguments
Summary
3 Variables
Terminology
Variables
Primitives
Integer Types
Overflow
Notations
Floating Point Types
Floating Point Constants
Infinity
NaN
Numeric Ranges
Casting
Boolean Types
Char
Pointers
References
Operators
Summary
4 Strings
Str
String Length
Extending a String
Capacity
Accessing a String Value
String Characters
Deref Coercion
Formatted String
Helpful Functions
Summary
5 Console Print
Positional Arguments
Variable Arguments
Named Arguments
Padding, Alignment, and Precision
Base
Developer Facing
Write! Macro
Display Trait
Debug Trait
Format! Macro
Console Read and Write Summary
6 Control Flow
The if Expression
The while Expression
The break and continue Keywords
The for Expression
The loop Expression
The loop break Expression
The loop Label
The Iterator Trait
Summary
7 Collections Arrays
Multidimensional Arrays
Accessing Array Values
Slices
Comparing Arrays
Iteration
Coercion
Vectors
Multidimensional Access
Iteration Resizing Capacity
HashMap
Creating a HashMap
Accessing the HashMap
Updating an Entry
Iteration
Summary
8 Ownership
Stack and Heap Memory
Shallow versus Deep Copy
Car Analogy
Move Semantics
Borrow
Copy Semantics
Clone Trait
Copy Trait
Clone Trait
Summary
9 Lifetimes
Introduction to Lifetimes
Function Headers and Lifetimes
Lifetime Annotation
Lifetime Elision
Complex Lifetimes
Sharing a Lifetime
Static Lifetimes
Structs and Lifetimes
Methods and Lifetimes
Subtyping Lifetimes
Anonymous Lifetimes
Generics and Lifetimes
Summary
10 References Declaration
Borrowing
Dereferencing
Comparing References
Reference Notation
Reference to Reference
Mutability
Limits to Multiple Borrowers
Summary
11 Functions
Function Definition
Parameters
Function Return
Const Functions
Nested Functions
Function Pointers
Function Aliases
Summary
12 Error Handling
Handling Error Handling
The Result Enum
The Option Enum
Panics
Panic! Macro
Handling Panics
Unwrapping
Match Pattern for Result and Option
Map
Rich Errors
Custom Errors
Summary
13 Structures
Alternate Initialization
Move Semantics
Mutability
Methods
Self
Associated Functions
Impl Blocks
Operator Overloading
Unary Operator Overloading
Binary Operator Overloading
Tuple Struct
Unit-Like Struct
Summary
14 Generics
Generic Functions
Bounds
The where Clause
Structs
Associated Functions
Enums
Generic Traits
Explicit Specialization
Summary
15 Patterns
Let Statement
Wildcards
Complex Patterns
Ownership
Irrefutable
Ranges
Multiple Patterns
Control Flow
Structs
Functions
Match Expressions
Match Guards
Summary
16 Closures
“Hello, World”
Closure Syntax
Closed Over
Closures as Function Arguments
Closures as Function Return Values
Implementation of Closures
The Fn Trait
The FnMut Trait
The FnOnce Trait
The move Keyword
The Impl Keyword
Matrix Example
Summary
17 Traits
Trait Definition
Default Functions
Marker Trait
Associated Functions
Associated Types
Extension Methods
Fully Qualified Syntax
Supertraits
Static Dispatch
Dynamic Dispatch
Enums and Traits
Summary
18 Threads 1
Synchronous Function Calls
Threads
The Thread Type
Processor Time
Builder
Communicating Sequential Process
Asynchronous Channel
Synchronous Channel
Rendezvous Channel
The try Methods
Store Example
Summary
19 Threads 2
Mutex
Nonscoped Mutex
Mutex Poisoning
Reader-Writer Lock
Condition Variables
Atomic Operations
Store and Load
Fetch and Modify
Compare and Exchange
Summary
20 Memory Stacks
Static Values The Heap
Interior Mutability
RefCell
OnceCell
Summary
21 Macros
Tokens
Declarative Macros
Repetition
Multiple Macro Matchers
Procedural Macros
Derive Macros
Attribute Macros
Function-Like Macros
Summary
22 Interoperability
Foreign Function Interface
Basic Example
Libc Crate
Structs
Bindgen
C Calling Rust Functions
Cbindgen
Summary
23 Modules
Module Items
Module Files
The path Attribute
Functions and Modules
The crate, super, and self
Keywords
Legacy Model Summary
Index
Register your copy of Programming with Rust on the InformIT site for convenient access to updates and/or corrections as they become available. To start the registration process, go to informit.com/register and log in or create an account. Enter the product ISBN (9780137889655) and click Submit. Look on the Registered Products tab for an Access Bonus Content link next to this product, and follow that link to access any available bonus materials. If you would like to be notified of exclusive offers on new editions and updates, please check the box to receive email from us.
About the Author
Donis Marshall has over 20 years of experience in designing and building enterprise software utilizing Microsoft technologies for leading companies across industry segments. As a Microsoft MVP and MCT, he has trained developers and engineers for many years. Donis is the author of the Programming Microsoft Visual C# (2005), Programming Microsoft Visual C# (2008), and Solid Code (2009).
1 Introduction to Rust
Welcome to Programming with Rust.
Your journey along the Rust super-highway begins here. Fasten your seat belt and prepare to enjoy an amazing journey of learning and exploration. Along this journey, you will uncover the benefits of Rust and how the language is positively changing the perception of modern programming languages. Each leg of the journey will inspect a different area of the language. In this manner, you will explore the entire language and emerge as a Rustacean, a professional Rust practitioner, and hopefully an active member of the growing Rust community.
The goals of this chapter include the following:
Providing an overview and description of Rust
Reviewing the Rust programming style
Listing the many benefits of Rust
Identifying and explaining Rust terminology
Reviewing the key elements of a Rust application
Creating your first Rust program
Introduction
Rust is a general-purpose language for creating safe, secure, and scalable applications. The language has features from several programming paradigms, as described shortly. Rust was originally designed as a systems programming language. However, it has emerged as a more versatile language capable of creating a variety of application types, including systems programming, web services, desktop applications, embedded
systems, and more. Although it may sound cliché, what you can accomplish with Rust is only limited by your imagination.
Different! That is an accurate assessment of Rust. Although the Rust syntax is based on the C and C++ languages, the similarity with other Cbased languages often ends there. In addition, Rust is not different just to be different; it is a difference with a purpose.
Rust’s borrow checker is an excellent example of a difference with a purpose. The borrow checker is a unique feature within Rust that promotes safe coding practices by enforcing rules related to the single ownership principal. No other language has this feature. For that reason, the borrow checker is a foreign concept to many developers but nonetheless invaluable.
In many ways, Rust represents lessons learned. Some of the unique features in Rust are lessons learned, or instances of a lack of success, from other languages. The willingness of Rust to depart from the normal script when necessary is a major feature of the language. Many programming languages struggle with effective memory management, for example. That is the purpose of the ownership feature in Rust—effective memory management.
Let’s be candid for a moment. Learning Rust can be frustrating at times. It requires an investment of time and the sacrifice of some brain cells. However, you will find that your investment in learning Rust is more than worthwhile. For example, learning to work with the borrow checker, not against it, is invaluable.
My goal in writing this book is to increase the number of Rustaceans— individuals with a high proficiency in the Rust programming language. My hope is that you will become an active member of the Rust community with your new mastery of the language. You have now officially started the journey toward becoming a Rustacean.
Functional Programming
Rust embraces a variety of programming paradigms, including functional programming, expression-oriented programming, and pattern-oriented programming. Let’s explore these various paradigms, starting with functional programming. Since Rust is our focus, I will present just a review of these programming models in this book.
What is functional programming? It is a programming model where functions are the essential building blocks of the language. With functional programming, functions are first-class citizens. You can use functions wherever a variable is normally found: a local variable, function parameter, or as a function return. A function can even perform operations on other functions, described as a higher-order function.
Rust is functional programming light. The language does not include every feature found in functional programming, such as lazy evaluation, a declarative programming style, tail call optimization, and more. However, Rust does support a functional style of programming.
Functional programming languages typically restrict procedural programming capabilities, such as global functions. As they are not incongruent, Rust allows for the comingling of procedural- and functionalstyle programming.
Pure functions are the centerpiece of functional programming. As a pure function, a function is fully described through its function interface. There is a direct correlation between the function parameters and a specific return value, without side effects. In addition, the results of a pure function should be repeatable. For example, a function that relies on an internal random number, making the results unpredictable, is not a pure function.
Immutability is an important ingredient of functional programming, which is a core tenet of Rust. Pure functions, for example, lean heavily on immutable state to eliminate side effects. Pointers, global variables, and references are generally omitted from a pure function to avoid side effects that can leak from a function.
To summarize, functional programming offers several benefits:
Added flexibility, with functions as first-class citizens
More transparency, with the focus on functions and not individual lines of code
Immutability, which makes programs easier to maintain by removing common problems such as side effects within functions
Expression Oriented
Rust is also an expression-oriented language, which is a programming style where most operations are expressions that return a value, instead of statements that return nothing. Expression-oriented programming is a close cousin of functional programming. All functional programming languages are also expression-oriented languages.
So what is a statement, and what is an expression?
Statements do not return a value, but they can cause a side effect. The possible side effects are unlimited, including database manipulation or updating a shared variable. Indeed, the purpose of some statements is the side effect.
Expressions are one or more operations that return a value, with minimal or no side effects. Pure functions are examples of expressions.
In Rust, expressions are preferred, even transfer of control statements such as the if and while statements are actually expressions.
The many benefits of expression-oriented programming include the following:
Without side effects, expression-oriented programs are easier to maintain.
The value of an expression is fully defined through its interface. This makes expressions more transparent.
Because expressions are interface driven, they are more testable.
Expression-oriented programming makes documentation easier. Without side effects, the expression can substitute as the documentation.
Expressions are more easily composed.
The ability to combine the various programming paradigms is another of Rust’s advantages.
Listing 1.1 provides an example of both functional and expressionoriented programming in Rust. The factorial function is a pure function with
no side effect. As an expression, the factorial function returns the result of the factorial calculation.
Code Listing 1.1. Factorial is a pure function and expression.
Click here to view code image fn factorial(n:i32) -> i32 { match n { 0..=1=>1, _=> n*factorial(n-1) }
Pattern Oriented
Typically, professional programming in Rust involves an abundance of patterns. Fair to say, patterns have a ubiquitous presence in Rust coding. This nod to pattern matching contributes to the uniqueness of the Rust programming style. Rust source code simply looks different from C++ or Java, for example!
Pattern matching is often associated with switch statements. For example, C++, Java, Go, and other languages have a switch statement. This is single dimensional pattern matching largely based on string or integral expressions. In Rust, pattern matching is extended to instances of userdefined types and sequences.
Rust has a match expression instead of a switch statement. However, pattern-oriented programming extends well beyond the match expression. Every instance of any expression in Rust is an opportunity for pattern matching. For example, even a simple assignment or conditional expression can result in pattern matching. This provides interesting ways of reimagining your code.
Pattern-oriented programming in Rust offers several benefits:
Patterns in Rust are highly expressive. This allows you to collapse complex code into simpler expressions. In Rust, pattern-oriented programming is a complementary model to expression-oriented programming.
Rust supports exhaustive pattern matching, which is more dependable and less error prone.
The display_firstname function demonstrates pattern matching, as shown in Listing 1.2. The function parameter is name, which is a tuple. The fields of the tuple are last and first. In the match expression, patterns are used to destructure the tuple, extract the first name, and print the name.
Code Listing 1.2. The display_firstname function displays a first name. Click here to view code image
Professionals regularly select Rust as their favorite programming language in a variety of recent surveys. Much of this recognition pertains to the unique features found in Rust.
Let’s explore the core features that contribute to Rust and its popularity.
Safeness
Safeness is an important crosscutting feature touching almost all aspects of the language. Safe code is robust, predictable, and not prone to unexpected errors. With these attributes, Rust provides a solid foundation where you can confidently develop applications. Immutable variables, the single ownership principle, and other features contribute to this objective.
In addition, Rust enforces safe coding practices at compile time. The ownership model with the borrow checker is a perfect example of this approach. At compile time, the borrow checker performs a series of checks, including ownership. If the ownership check fails, the borrow checker presents an explanation and the compilation does not complete successfully.
Several factors contribute to safe code in Rust:
Immutability is the default, which prevents inadvertent changes
The enforcement of proper lifetimes to prevent anti-patterns, such as dangling references
References for safe pointers
A “resource acquisition is initialization” (RAII) strategy for variably sized resources, such as vectors, for dependable memory management
Ownership
The ownership feature provides safe memory access using the single owner principle. This principle consigns a single owner to variables, and never more than one owner.
This approach prevents sharing ownership of the same memory. Race conditions, unstable variables, and dangling references are some of the potential problems mitigated with this approach. There are exceptions for sharing ownership as presented later in this book.
Let’s use a car analogy to demonstrate the single owner principle. Here are the basic facts: There is a car, and Bob is its sole owner.
Now here are two scenarios:
Bob has the car.
Ari occasionally wants to drive the same car.
As the owner, Bob can always drive the car, except when he has loaned it to someone else. If someone else (Ari) wants to drive the car, there are two possibilities: Bob must either sell or lend the vehicle to Ari. Either way, Bob loses possession of the vehicle, at least temporarily.
Here are the steps if Bob lends Ari the car.
Bob drives the car.
Bob lends the car to Ari. Ari drives the car. When Ari is done, he returns the car to Bob.
Bob drives the car.
The borrow checker is responsible for enforcing correct ownership, including lending, at compile time. We will unlock the mystery of the borrow checker later in the book with the goal of making the borrow checker your best friend.
Lifetimes
Lifetimes is a feature in Rust that prevents accessing values that are no longer available. A reference is a basic pointer in Rust. If allowed, improper access to dropped values can cause hanging references and potential program failure. The result would be vulnerable applications that are both unstable and unpredictable. Rust eliminates this problem with the lifetimes model.
The borrow checker manages lifetimes. You are notified at compile time of invalid lifetimes. These sorts of problems are better isolated at compile time, not at runtime.
When there is ambiguity related to lifetimes, lifetime annotations are hints to the borrow checker about the proper lifetime. If determining the lifetime is trivial, lifetime annotations are not required. The borrow checker will just know. This is called lifetime elision.
The benefit of the lifetimes feature is a stable memory environment without the worry of hanging references.
Fearless Concurrency
Fearless concurrency is important and worthy of inclusion in the list of major Rust features. Fearless concurrency provides a safe environment for concurrent programming. In many ways, this safe environment is created from the benefit of the beforementioned features. For example, the ownership model in Rust largely eliminates race conditions in concurrent programming.
When transitioning from sequential to concurrent programming a process called hardening is often undertaken to ensure a safe environment
for multithreaded code. Removing global variables, as shared data, is typically one step in the hardening process. Fearless concurrency eliminates the need for hardening.
Concurrent programming is often considered the bogeyman of coding. It can add complexity and make applications less maintainable. Worst of all, problems in concurrent programming are often not found until runtime. Fearless concurrency creates a safer environment for concurrent programming.
Zero-Cost Abstraction
Zero-cost abstraction is a feature of Rust features. Yes, you read that correctly. For that reason, it is the final feature mentioned here. Zero-cost abstraction is the policy that Rust features should not incur a performance penalty at runtime, if avoidable.
Generational garbage collection is an intrinsic feature of several popular managed languages, including Java, C#, and Go, for managing dynamic memory. Garbage collection can be costly and nondeterministic. As such, you never know when garbage collection may occur. Compare this to Rust, where there is no memory model. Absolutely none! Ownership, as described earlier in this chapter, provides deterministic memory management without overhead. That is an example of zero-cost abstraction.
Rust Terminology
Many technologies, including programming languages, have their own terminology. Familiarizing yourself with that terminology can be helpful when communicating with peers and others in the larger Rust community.
For Rust, the motif is crates, as in shipping crates. Here are some of the important terms in Rust. These will help you talk Rust.
Rust: Let’s start with the most important term: “Rust” itself. Rust is not an acronym or a special technology term. The name Rust comes from the term rust fungi, which is a robust pathogen that attacks living plants.