Skip to main content

145 docs tagged with "rust"

View all tags

`match` Expressions (Part 1): The Basics of Pattern Matching

We've seen how to use if-else and else if to make decisions in our code. However, when you have a long chain of else if statements, it can become clumsy. For more complex conditional logic, Rust has a powerful control flow construct called match. A match expression allows you to compare a value against a series of patterns and then execute code based on which pattern matches.

`match` Expressions (Part 2): Advanced Patterns

In the previous article, we learned the basics of match expressions. Now, we're going to explore some of the more advanced patterns that make match one of Rust's most powerful features. These patterns allow you to write even more expressive and concise conditional code.

`panic!` and Unrecoverable Errors

In the previous article, we learned how to handle recoverable errors with the Result enum. However, sometimes errors are not recoverable. For these situations, Rust has the panic! macro.

Advanced Lifetimes

We've covered the essentials of Rust's lifetime system, including explicit annotations and elision rules. While these cover the vast majority of cases, some complex scenarios require a deeper understanding of lifetimes to model correctly. This article explores three advanced lifetime features: lifetime subtyping, lifetime bounds on generic types, and lifetimes on trait objects.

Associated Types

We have mastered generics and traits, but Rust's trait system has even more power to offer. In this article, we introduce associated types, a powerful feature that connects a type placeholder with a trait. This allows a trait's methods to use these placeholder types in their signatures, often leading to clearer and more flexible code than using generics alone.

Async/Await in Rust: An Introduction

We've covered a lot of ground in our exploration of Rust's concurrency features. Now, we're going to dive into a new paradigm: Async/Await in Rust.

Basic Console Input and Output: Using the `std::io` module

Welcome to our third series! Now that you have a strong foundation in Rust's basic syntax and data types, it's time to start building programs that can interact with the user. In this article, we will explore how to get input from the user and display output to the console using Rust's standard I/O library.

Basic Operators: The Tools of Computation

Now that you have a solid understanding of comments and documentation, let's get back to the core of computation: operators. Operators are special symbols that perform operations on values and variables. Rust supports a range of operators that you will be familiar with from other languages, including arithmetic, comparison, logical, and bitwise operators.

Box<T> for Allocating Data on the Heap

Welcome to a new chapter on some of Rust's most powerful memory management features. We'll begin our exploration of smart pointers with the most straightforward one: Box. A Box is a smart pointer that allows you to store data on the heap instead of the stack. It's a fundamental tool for managing memory and enabling certain data structures in Rust.

Combining Loops and Conditionals

You've now learned about the two fundamental pillars of control flow: conditionals (if) and loops (loop, while, for). The real power comes when you start combining these two concepts to build more complex and interesting logic. In this article, we'll explore some common patterns for using if expressions inside loops.

Combining Rc<T> and RefCell<T> for Multiple Owners of Mutable Data

We've seen how Rc enables multiple owners and how RefCell allows interior mutability. Individually, they solve specific problems. But when combined, they unlock a powerful pattern for managing shared, mutable state in a single-threaded context. The combination Rc> allows a single piece of data to have multiple owners, any of whom can mutate it.

Comments and Documentation: Writing Readable Code

After learning how to write functions in Rust, it's crucial to learn how to explain what those functions do. Writing clear, readable code is not just about the code itself; it's also about the comments and documentation that accompany it. In Rust, documentation is a first-class citizen, and the tooling makes it easy to write and generate high-quality documentation.

Common Collection Methods

We've now learned about the three most common collection types in Rust: Vec, String, and HashMap. While each has its own specific use case, they share a common foundation through the Iterator trait. This means they have a rich set of methods for performing common operations like sorting, filtering, and mapping. In this article, we'll explore some of these useful methods.

Conditional Statements: `if-else if-else` (Part 2)

In the previous article, we learned the basics of if-else expressions. This is great for handling a single condition, but what if you have multiple conditions you want to check? For this, Rust provides the else if expression.

Conditional Statements: `if-else` expressions (Part 1)

Welcome to our fourth series! Now that you've built some complete console applications, it's time to dive deeper into one of the most fundamental concepts in programming: control flow. Control flow allows you to run different blocks of code based on certain conditions. The most common way to do this is with if expressions.

Core Concepts: Compiled Language and Static Typing

Following our exploration of Rust's wide range of applications, this article dives into two of the most fundamental concepts that underpin Rust's power: its nature as a compiled language and its use of static typing. Understanding these concepts is essential for grasping how Rust achieves its impressive performance and reliability.

Dangling References and the Borrow Checker

Having explored Mutable References, we've seen how Rust enforces strict rules to prevent data races. Now, we turn to another critical safety guarantee provided by the ownership system: the prevention of dangling references. This article puts the borrow checker to the test, demonstrating how it acts as a vigilant guardian to eliminate one of the most treacherous bugs in systems programming.

Default Generic Type Parameters and Operator Overloading

After mastering Associated Types, we'll now explore two related features that make Rust's generic system even more ergonomic and powerful: default generic type parameters and operator overloading. Default parameters reduce the amount of boilerplate needed for common generic cases, while operator overloading allows us to specify custom behavior for operators like + and - on our own types.

Defining Enums: A Way to Enumerate Possibilities

In the previous series, we learned how to group related data together using structs. Now, we're going to look at another powerful way to define custom types in Rust: enums. An enum, or enumeration, allows you to define a type by enumerating its possible variants.

Defining Methods on Structs with `impl`

We've learned how to define structs to store data. Now, let's learn how to add behavior to our structs by defining methods. Methods are functions that are associated with a specific struct and can operate on its data.

Deleting Data

We've learned how to insert and update data. Now, let's learn about Deleting Data.

Deref Trait for Treating Smart Pointers like Regular References

After learning how to allocate data on the heap with Box, we'll now explore the magic that makes smart pointers so convenient. The Deref trait is the key to this convenience, as it allows a smart pointer struct to be treated like a regular reference. This enables you to write code that works with both references and smart pointers seamlessly.

Extractors in Actix

We've seen a little bit of Extractors in Actix in previous articles. Now, let's take a closer look.

Fully Qualified Syntax for Disambiguation

After learning how to empower our types by implementing traits for them, a new question arises: what happens if a type implements two different traits that both have a method with the same name? Or what if a struct's own method has the same name as a method from a trait it implements? Rust is designed to be unambiguous, and it provides fully qualified syntax as a powerful tool to tell the compiler exactly which method you intend to call.

Functions in Rust: The Core of Reusability

After exploring Rust's fundamental data types, including String and &str, it's time to look at how we can organize our code into reusable blocks. Functions are the primary way we do this in Rust. You've already seen one of the most important functions in Rust: the main function. Now, let's learn how to create our own.

Fundamental Data Types: Compound Types

In our previous article, we explored Rust's scalar data types, which represent single values. Now, we'll look at compound types, which can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

Fundamental Data Types: Scalar Types

After learning about variables, mutability, and shadowing, the next logical step is to explore the different types of data that you can store in those variables. Rust is a statically typed language, which means that every value has a type, and the compiler must know the type of every variable at compile time.

Generic Data Types

Welcome to a new chapter in your Rust journey! Having mastered the ownership system, we now turn to another of Rust's superpowers: generics. Generics are a fundamental tool for abstraction, allowing us to write code that is flexible and reusable across many different data types without sacrificing the performance and safety that Rust guarantees.

Generics in Structs and Enums

In our previous exploration of Generic Data Types, we saw how to reduce code duplication by creating generic functions. Now, we'll extend this powerful concept to our custom data types. This article dives into how to define and use generics in structs and enums, allowing us to build flexible, reusable data structures that are central to idiomatic Rust.

Handling User Input: Parsing Strings

In the previous article, we learned how to get input from the user as a String. However, to build useful applications, we often need to convert that string into a different type, like a number. This process is called parsing. In this article, we'll explore how to parse strings into numbers and handle the potential errors that can occur.

Implementing Traits on a Type

Now that we've seen how to define shared behavior by Defining a Trait, the next logical step is to provide the actual implementation for that behavior. This article covers the mechanics of implementing a trait on a type, which is how you fulfill the "contract" a trait defines. We'll also explore the "orphan rule," a core principle that ensures coherence and prevents conflicts in the Rust ecosystem.

Introduction to Structs: Creating Custom Data Types

Welcome to Chapter 2! We've built a solid foundation with Rust's basic data types, collections, and control flow. Now, we're going to start exploring some of Rust's more powerful features that allow you to create complex and expressive programs.

Iterating over Collections

In the previous article, we introduced the for loop and used it to iterate over a range of numbers. The true power of for loops, however, comes from their ability to iterate over collections like arrays, vectors, and more. This is the most common and idiomatic way to work with collections in Rust.

Iterating over Collections: The `Iterator` Trait

We've seen how to use for loops to iterate over collections. But how does it actually work? The magic behind for loops is the Iterator trait. In this article, we'll take a closer look at this trait and the different ways you can create an iterator from a collection.

Lifetime Elision

In the previous article, we learned how to use explicit lifetime annotations to help the compiler ensure reference validity. You might have been thinking that writing 'a and 'b everywhere could get tedious. You're right! The Rust team thought so too. That's why the compiler has a powerful feature called lifetime elision, which allows it to infer lifetimes in common, predictable patterns, saving you from writing explicit annotations most of the time.

Lifetimes: Ensuring References are Valid

We've mastered generics and traits, but there's one more piece to the puzzle of Rust's type system: lifetimes. Lifetimes are the mechanism the borrow checker uses to ensure that all references are valid. While we've seen the borrow checker in action preventing dangling references, lifetimes are the syntax we use to give the compiler hints in situations where it can't figure out the relationships between references on its own.

Loops: `for` loops and the `..` range operator

We've explored loop for infinite loops and while for conditional loops. Now, we'll look at the most common and often most powerful loop in Rust: the for loop. for loops are excellent for executing a block of code a specific number of times and for iterating over collections.

Loops: `while` loops

In the previous article, we learned how to create infinite loops with the loop keyword. While loop is useful, it's often more common to loop while a certain condition is true. For this, Rust provides the while loop.

Message Passing to Transfer Data Between Threads with Channels

Following our exploration of Threads: Creating and Managing Threads, we'll now dive into Message Passing to Transfer Data Between Threads with Channels. This is a powerful technique for communication between threads that helps ensure safety and prevent bugs.

Modules and `use`: Organizing Code

As your programs grow larger, it becomes important to organize your code to keep it manageable. Rust provides a powerful module system for this purpose. In this article, we'll explore how to group code into modules and how to bring items into scope with the use keyword.

Move, Clone, and Copy

Building on our understanding of The Stack and the Heap, we can now formalize the concepts of how data is transferred in Rust. This article focuses on the three key mechanisms that govern data assignment and duplication: Move, Clone, and Copy. Mastering these concepts is crucial for writing efficient Rust code and for working harmoniously with the ownership system.

Mutable References

We've seen how References and Borrowing allow us to access data without taking ownership. While immutable borrows provide safe, shared read access, sometimes we need to modify the data we're borrowing. This is where mutable references come in. They grant temporary, exclusive write access, and understanding their rules is key to mastering idiomatic and safe Rust.

Ownership in Action: A Practical Example

Throughout this series, we have explored the intricate rules of Rust's ownership system, from moves and copies to immutable and mutable borrows. Now it's time to consolidate that knowledge. This article brings everything together in a single, practical example, demonstrating how these concepts work in harmony to create a safe and efficient program.

Pattern Matching with `match`

We've seen the match expression a few times already, especially when working with enums like Option and Result. In this article, we're going to take a deeper dive into what makes match so powerful: pattern matching.

Project: Building a Simple Calculator

It's time to put everything you've learned in this series into practice. We've covered variables, operators, and handling user input. Now, we'll combine all of these concepts to build a complete, interactive program: a simple command-line calculator.

Project: Building a Simple Linked List with Smart Pointers

This series has covered a wide range of smart pointers and memory management techniques. Now it's time to put that knowledge into practice with a classic computer science data structure: the singly linked list. This project will demonstrate why Box is essential for recursive data structures and how to handle ownership and memory management correctly.

Project: Guessing Game (Part 1)

In our last project, we built a simple calculator. Now, we're going to start a new, slightly more complex project that we will build over the next few articles: a classic guessing game.

Project: Guessing Game (Part 2)

In Part 1 of our Guessing Game project, we set up the project and generated a random secret number. Now, it's time to add the core logic of the game: getting the user's guess and comparing it to the secret number.

Project: Guessing Game (Part 3)

Welcome to the final part of our Guessing Game project! In Part 2, we added the core logic for comparing the user's guess to the secret number. In this article, we'll add the final polish to our game by handling invalid input gracefully and improving the looping mechanism.

Project: Modeling a User Account

It's time to put everything we've learned about structs into practice. In this project, we will model a user account for a web application. This will involve defining a User struct, creating instances of it, and defining methods to interact with the user data.

Project: Rectangle Area Calculator (Part 1)

To solidify our understanding of structs, let's build a simple program that calculates the area of a rectangle. This project will be broken into two parts. In this first part, we will focus on defining the Rectangle struct and creating a simple main function to work with it.

Project: Rectangle Area Calculator (Part 2)

In Part 1 of this project, we wrote a program that calculates the area of a rectangle using a standalone function. This works, but we can make the code clearer and more organized. The area function is directly related to the Rectangle struct, so it should be a method of the struct.

Project: To-Do List

To wrap up our series on collections, we're going to build one more project: a simple command-line to-do list application. This project will primarily use a Vec to store the to-do items and will allow the user to add items, list items, and mark items as complete.

Project: Word Counter

It's time to put everything we've learned about collections into practice. In this project, we will build a program that counts the occurrences of each word in a given text. This is a classic programming exercise that is a perfect way to use the HashMap to solve a real-world problem.

Putting It All Together: A Generic Function with Lifetimes and Trait Bounds

This is the capstone article for our series on Rust's advanced type system. We've individually explored generics, traits, and lifetimes. Now, we will see how these three powerful features combine to create a single function that is fully abstract, yet completely type-safe. This is where the true expressive power of Rust's type system shines.

Querying Data with Diesel

We've learned how to define a schema and write migrations. Now, let's learn about Querying Data with Diesel.

Rc<T>, the Reference Counted Smart Pointer

We've seen how Box enforces single ownership of heap-allocated data. But what happens when a single value needs to have multiple owners? For example, in a graph data structure, several edges might point to the same node, and that node shouldn't be cleaned up until all those edges are gone. For this, Rust provides the Rc smart pointer, which enables Reference Counting for shared ownership.

RefCell<T> and the Interior Mutability Pattern

We've seen how Rust's borrowing rules are enforced at compile time, and how Rc allows shared ownership of immutable data. But what if we need to mutate data that is shared and theoretically immutable? For this, Rust provides the interior mutability pattern, and its primary tool is the RefCell smart pointer.

Reference Cycles and How to Prevent Them

The Rc and RefCell patterns are incredibly powerful, but they come with a potential danger: reference cycles. A reference cycle is a situation where items reference each other in a loop, causing their reference counts to never drop to zero. This is a form of memory leak, and while Rust's ownership system prevents most memory safety issues, this is one you must handle carefully yourself. In this article, we'll learn how to create a reference cycle and how to prevent it using weak references.

References and Borrowing

After mastering Move, Clone, and Copy, you understand how ownership is transferred or duplicated. However, moving ownership can be restrictive, and cloning can be inefficient. What if we just want to let a function use a value for a while without taking ownership? This is the problem that references and borrowing solve. This mechanism is the key to writing flexible, efficient, and safe Rust code.

Routing in Actix

We've learned how to handle requests and responses. Now, let's talk about Routing in Actix.

Rust Z2H

Welcome! This interactive online book is your comprehensive guide to mastering the Rust programming language. Whether you are a complete novice taking your first steps into the world of code or a developer looking to solidify your Rust skills, this resource is crafted for you.

Setting Up Your Development Environment (Part 1): The Rust Toolchain

Now that we've explored the Rust ecosystem with Cargo and crates.io, it's time to get our hands dirty and set up a professional development environment. In this article, we will install the Rust toolchain using rustup and configure Visual Studio Code with the essential rust-analyzer extension.

Slices as Borrows

After seeing how the Borrow Checker guarantees the validity of references, let's look at a special kind of reference that is ubiquitous in Rust: the slice. Slices provide a way to reference a contiguous sequence of elements in a collection rather than the whole collection. They are a powerful tool for writing expressive and efficient code that operates on portions of data, like a substring or a sub-array.

Slices: A View into a Collection

We've learned about several collection types like String and Vec. A slice is a different kind of collection. It doesn't have ownership of its data. Instead, a slice is a reference to a contiguous sequence of elements in another collection. This allows you to work with a part of a collection without having to copy the data.

Static and Bounded Trait Objects

We've explored the depths of lifetimes and traits, and now we'll see how they intersect in the world of trait objects. A trait object, like &dyn MyTrait, allows for dynamic dispatch, but it's still a reference and thus has a lifetime. Understanding the lifetime bounds on trait objects, especially the default 'static bound, is crucial for writing flexible and safe polymorphic code.

Storing UTF-8 Text with Strings (`String`)

We've already touched on Rust's two main string types, &str and String. In this article, we're going to take a deeper dive into the String type, which is a growable, mutable, owned, UTF-8 encoded string type. It is one of the most common and useful collection types in Rust.

String Types: `&str` and `String`

After exploring compound types like tuples and arrays, we need to address one of the most common and important data types in any language &str (a string slice) and String.

Structs and Ownership

We've learned about structs and we've learned about ownership. Now, it's time to see how these two concepts interact. Understanding how ownership works with structs is crucial for writing correct and efficient Rust code.

Supertraits: Requiring One Trait's Functionality Within Another Trait

Sometimes, when writing a trait, you need to rely on functionality from another trait. Rust allows you to do this using a feature informally called supertraits. A supertrait is a trait that another trait depends on. This lets you build hierarchies of behavior, where one trait requires and can use the methods of its parent trait, creating a powerful and expressive API.

The 'What' and 'Why' of Rust (Part 2): A Universe of Applications

Following our exploration of Rust's core philosophy and history, this article delves into the practical applications of Rust across different domains. This exploration is essential for understanding where Rust shines and why it has become a go-to language for performance-critical and safety-critical development.

The `Debug` Trait and Printing Structs

So far, when we've wanted to inspect a value, we've used the println! macro with the {} format specifier. However, if you try to do this with a struct you've defined, you'll run into an error.

The `if let` and `while let` Control Flow Constructs

We've seen how powerful the match expression is for pattern matching. However, sometimes a match can be a bit verbose, especially when you only care about one of the possible cases. For these situations, Rust provides a convenient shorthand: if let.

The `Option` Enum: Handling the Absence of a Value

In the previous article, we learned how to define our own enums. Now, we're going to look at a very special and common enum from the Rust standard library: Option. The Option enum is Rust's answer to the problem of null values, which are a common source of bugs in many other programming languages.

The `Result` Enum and Error Handling

In the previous article, we learned how the Option enum is used to handle values that might be absent. Now, we'll look at another crucial enum from the standard library: Result. The Result enum is the primary way that Rust handles operations that can fail, such as reading a file or parsing a string.

The Drop Trait for Custom Cleanup Logic

We've spent a lot of time exploring how Rust's ownership system manages memory. But what happens when a value goes out of scope? The compiler automatically inserts a call to a special trait: Drop. The Drop trait allows you to run custom code when a value is about to be deallocated, enabling the powerful RAII (Resource Acquisition Is Initialization) pattern that is central to safe and idiomatic Rust.

The newtype Pattern to Implement External Traits on External Types

We've seen how to build hierarchies of traits with supertraits, but what happens when we hit a wall with the compiler's rules? One of the most important rules governing traits is the orphan rule, which prevents us from implementing an external trait on an external type. In this article, we'll explore a clever and idiomatic Rust solution to this problem: the newtype pattern.

The Rust Ecosystem: Cargo and Crates.io

Following our exploration of Rust as a compiled and statically typed language, we now turn our attention to the tools that make the Rust development experience so productive and enjoyable: Cargo and crates.io. These two components are the heart of the Rust ecosystem, enabling developers to build, manage, and share code with ease.

The Stack and the Heap

Following our introduction to What is Ownership?, this article dives deeper into a crucial aspect of memory management that underpins the entire ownership system: the Stack and the Heap. Understanding how Rust organizes memory is essential for writing efficient, high-performance code and for truly grasping why ownership works the way it does.

Threads: Creating and Managing Threads

Following our introduction to Fearless Concurrency, this article delves into Threads: Creating and Managing Threads. This is the next logical step in our journey to mastering concurrent programming in Rust.

Trait Bounds

We've defined traits and implemented them on types. Now it's time to connect these concepts to generics to unlock their full potential. In this article, we'll explore trait bounds, the syntax we use to constrain generic types. This is how we inform the compiler that a generic type T must have certain behaviors, finally allowing us to solve the largest function problem we encountered earlier.

Traits: Defining Shared Behavior

After exploring how to make our data structures generic with Generics in Structs and Enums, we're ready to tackle the final piece of the puzzle: traits. A trait is a language feature that tells the Rust compiler about functionality a type must provide. It's Rust's way of defining shared behavior, similar to interfaces in other languages, and it's the key to unlocking the full power of generics.

Transactions

We've learned how to delete data. Now, let's learn about Transactions.

Tuple Structs and Unit-Like Structs

In the previous article, we learned how to define structs with named fields. Rust also provides two other variations of structs that are useful in different scenarios: tuple structs and unit-like structs.

Using `if` in a `let` Statement

In the previous articles, we explored how if-else expressions can be used to control the flow of our program. Now, we're going to look at a particularly powerful feature of Rust: because if is an expression, we can use it on the right side of a let statement to conditionally assign a value to a variable.

Variables and Mutability: The Building Blocks of Rust

Welcome to our second series on Rust! Now that you have a solid understanding of the Rust ecosystem and the structure of a Rust program, it's time to dive into the fundamental building blocks of the language. We'll start with one of the most basic concepts in any programming language: variables.

Vectors (`Vec<T>`): The Growable Array

Welcome to our fifth series! Now that you have a solid understanding of Rust's basic data types and control flow, it's time to dive into collections. Collections are data structures that can contain multiple values. Unlike the array and tuple types we've seen before, some collection types can grow or shrink in size.

Weak<T> to Create Non-Owning References

In the last article, we saw how Rc can create reference cycles, leading to memory leaks. To solve this, Rust provides a companion smart pointer:Weak. A Weak is a non-owning reference that allows you to refer to data owned by an Rc without contributing to its strong reference count. This is the primary tool for breaking reference cycles and creating temporary, safe references to data.

What is Ownership?

Welcome to the first article in our series on one of Rust's most distinctive and empowering features: the ownership system. This concept is the key to how Rust achieves its ambitious goal of memory safety without needing a garbage collector, setting it apart from many other modern programming languages. Understanding ownership is fundamental to writing effective, safe, and concurrent Rust code.

Working with Futures

In our last article, we were introduced to Async/Await in Rust. Now, let's take a closer look at the Future trait, which is the foundation of async/.await.

Working with Middleware

We've learned about handling form submissions. Now, let's talk about Working with Middleware.

Working with Variables and Scope

After learning how to handle user input and parse strings, it's a good time to take a step back and understand a crucial concept that underpins Rust's safety guarantees: scope. Understanding how scope works is essential for writing correct and bug-free Rust code.

Your First Rust Program: 'Hello, World!' (Part 1)

After setting up and configuring your development environment, you are now ready to write your first Rust program. The "Hello, World!" program is a tradition in computer programming. It's a simple program that outputs "Hello, World!" to the screen. This tradition is a great way to make sure that your environment is set up correctly and to get a feel for a new language.

Your First Rust Program: 'Hello, World!' (Part 2)

In the previous article, we created and ran our first Rust program using cargo run. Now, it's time to understand the code we ran. We will dissect the "Hello, World!" program line by line to understand the basic structure of a Rust application.