Tutorials on Rust

Learn about Rust from fellow newline community members!

  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL
  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL

Introduction to Results in Rust

Result s are a fundamental concept to grasp to become a proficient Rust developer, but they can feel a little weird coming from JavaScript. But if we take it slow, they're not that hard to understand: Result is just a way to handle errors with a type . Below, I'm going to guide you through the ins and outs of the Result concept in the Rust programming language. Coming from JavaScript, one of the first stumps beginner Rust developers hit is on error handling . Rust statically types errors and treats them as any other data type, consolidating most of the error treatment code around the Result type . This is a lot different than exception-based approaches, so it's worth a closer look. A Result is a data type that is expected to be returned from functions that can fail in an expected and recoverable way. So put another way, a Result is "just" the return type of a function that is not guaranteed to work. But what kind of functions are not guaranteed to work? Imagine a function that divides two integers. If 6 and 2 are parameters, then the answer will be 3. We might be inclined to think our function's return type is an integer as well, but hold up. Can we guarantee that a division between two integers will always work? Well, let's say we have 6 and 0 as inputs. What should be the answer in this case? What integer would be a valid answer in this case? That's right, there is none; Because 6 divided by 0 is not an integer - here it's a failure. A perfect case for the Result type. Because our integer division function won't return an integer, we need to return something else. In this case, we'll use a Result to wrap the integer when there is a success, or the Result will wrap the explanation of why an integer could not be generated. Because this function is fallible it returns a Result so that the user can deal with its failure properly. This is also an important part of how we use Result : errors that cannot be anticipated, cannot be handled properly upstream. If we want to properly define and type this function, we must know beforehand that it can fail . We must know 6 divided by 0 cannot output an integer and plan for that if we intend to always output the correct data type. An unexpected failure cannot be annotated (because we don't know what it is yet) and it's handling will be impossible. However, an expected failure can have it's failure "encoded" as a Result which will allow us to properly handle the failure case. Result formally tells the caller of a function that the function call can "fail" -- this means the caller is not surprised when the call fails, and we have alternative code paths for when it happens. Common alternative paths involve: One feature of the Rust compiler is that it will require Result to be handled . There are several ways you can do this, if you don't provide and handling code it will cause the compiler to raise a warning. This feature is great because we can be fearless about the execution of our programs -- we know that when the compilation returns no warning flags for our code, we've handled all of the Result possibilities. You might think that the definition of Result resembles JavaScript Errors or Ruby Exceptions . Exceptions on these languages have similar intentions as our Result s here, but there are two crucial differences to note. 1 . Exceptions represent the error, while Results represent the whole outcome Let's think back to our integer division example. The function returns a Result so it can account for the outcome when there is a division by 0 . However, if the division works properly the returned data type is still a Result . The Result is returned either way because it contains the operation outcome as a whole. Now, let's think about Exceptions. We usually see them only when something went wrong . The successful path just returns the naked data type (the integer in our case). In other words, the function would yield the exception or the successful data type. Kind of weird, right? Result gives us consistency because we always get a Result regardless of success or failure. 2 . Exceptions are special, while Results are just a regular type When talking about Exceptions above, you might have taken note of the "yield" verb. Why couldn't I have just used the "return" verb? An Exception is a special kind of data type on languages that implement it. They are generally seen together with verbs like "raise" and "throw", which are reserved keywords used to notify the runtime that something bad happened. Getting more technical, we could say an Exception is thrown, which will reverse the execution stack until it's properly dealt with. This means Exceptions are special to the runtime and have special keywords defined so they can be interacted with. Results, on the other hand, are just a data type that you return from a function. The norm is to treat them like you would treat any other data type. This lowers the cognitive load. Also, because Result is "just a regular type", Result s are part of the type signature for every function they are used in: so you'll never be surprised when a Result is returned. The compiler won't let you be surprised: it will tell you beforehand and make sure you handle it. At this point, I hope you understand the basic rationale behind Result . But let's figure out how to read it and use it in more practical situations. The Result in Rust is defined as an enumeration of two options, with two generic types . Here is the definition directly from Rust's code. The two generic types, T and E , represent the type of the successful and failed outcomes respectively. These values will each be contained by the Result. Each constructor carries one of these types, so we have a constructor for successes ( Ok ) and another for failures ( Err ). Going back to our example, our integer division example returns a Result<u32, String> , which means the operation can either: The construction of a "success", would be the number wrapped by an Ok constructor ( 6 / 2 = Ok(3) ). The failure, on the other hand, would be wrapped by an Err constructor ( 6 / 0 = Err("Division by zero!") ). Say that after we perform the integer division we want to do another operation with the outcome. Let's say we want to divide 6 by 2 , then double the result . We could naively try to multiply the Result by 2, which would promptly yield a compiler error: The error you see is the compiler looking out for us. It letting us know that we are assuming an operation-that-can-fail will never fail. In other words, we are trying to multiply the successful outcome by 2, but we aren't handling the case where the division has failed. So how should the program react in the case where our division fails? Well, in this case, we know the division can never fail since the inputs are the constants 6 and 2, which we know will result in Ok(3) . In these cases, we can tell Rust we are super-sure-and-positive the error will never happen, so just trust us: The unwrap function does this: it removes the Result from the equation. Of course, you're probably wondering, what happens if the outcome was a failure? In our case, the error would be associated with a String type, which cannot be multiplied by two. That's the risk you take when you unwrap a Result . You, as a developer, are taking the responsibility and vowing the Result will never be a failure. If it is, Rust will crash your whole program with a panic . At that point, nothing can stop the inevitable crash. Scary, huh? So what should we do if we are not sure the Result is always a success? Or if you are simply not comfortable taking this kind of risk? For that, we'll use Result matching . Unwrapping a Result is risky. Unwrapping is only safe if there is no way for the error to happen, which is unusual in complex systems. In real-world programs, the arguments often come from the outside (like user input) - we just can't be sure that the division will work or not. For these cases, we would like to know if the operation succeeded or failed without risking a crash. Rust has a powerful pattern matching implementation that works perfectly for us in this case. For example, we could use it to default an error to a constant value (like 0 ), for example: The match keyword lets us test against Result constructors and associate a path of execution to each. And this example above shows how to provide a default value for a failure. But as we said before, we could also pass the error "up the chain". Let's see what it looks like: Now we are passing the problem to the caller, just like the integer_divide function does. If the division fails, we don't know what to do, so we just return the Result . Note that in this case, we have to wrap the successful result in an Ok constructor so function signature is respected. This pattern of unwrapping or returning the result is so prevalent and useful, we Rust developers got tired of writing it every time. Rust has a macro that does just that for us . This looks better, doesn't it? The star of the show here is the ? operator. The funny thing about this example is that both the match and the ? are the same from the compiler's point of view. The macro and operator just make it easier to read. So where to go from here?

Thumbnail Image of Tutorial Introduction to Results in Rust

Rust Iterators: A Guide

By the time you walk out of here, you should understand what iterators are good for, how they work internally, and how to create your own!Iterators are a fairly central concept to Rust. If you're looping over something, you're very likely already using an iterator. If you're transforming collections, you probably should be using them. If your function returns a lazily evaluated sequence of things, you should consider returning an iterator - especially if that sequence could be lazily evaluated. We'll take a look at how iterators are implemented, how to iterate over a collection, what sorts of iterators exist in the standard library, usage and common patterns for transforming data, and finally a few examples of useful crates that provide their functionality via powerful iterators. Skill-wise, you'll ideally have an understanding of I highly recommend having some sort of environment to run snippets of code in. The simplest thing to use is the Rust Playground . If you'd like a local environment, refer to The Book for guidance on setting that up. Essentially, an iterator is a thing that allows you to traverse some sort of a sequence. Note that since Rust's iterators are lazy, this sequence could be generated on the fly - you could just as well traverse an existing array of finite length or create an iterator that keeps spewing out random numbers infinitely. In Rust, iterators are typically implemented using the Iterator trait. All we need to implement that trait for our custom type is provide an associated type Item (this is the type of the elements of the sequence, returned by the iterator) and the next method. Let's try and implement an iterator over numbers from 1 to 10. The next method is expected to yield the next element of the sequence. It takes a mutable reference to self in case we need to keep track of some state between next calls, which is normally the case. We'll soon find it useful. The return type of next is Option<Self::Item> . If an iterator is finite, it needs to return None to indicate it has no more elements to return. Right now, this iterator will immediately finish, not yielding any items. Let's fix this. This should be pretty self-explanatory. Every call to next increments self.current and yields it until it grows beyond 10. So we have an iterator. Let's use it. The most basic thing we can do is simply loop over all of its elements: There's actually an Iterator implementation for the Range type in Rust! A Range is what you get when you type something like 1..5 . Instead of writing the above custom iterator, we could have simply done this: Sometimes you'll want to provide the user of your code the ability to iterate over your type, but without that type itself being an iterator. This will be the case with collections. If you implement your own vector type, you probably don't want that type to needlessly hold an extra iteration variable just in case someone wants to iterate over it. What we want is a way to create an iterator out of the collection. There are three mechanisms you'll typically see. If we want to iterate over a vector of chars, we could do something like this: If we leave out the into_iter() call, Rust will call that method implicitly anyway. This implicit call is important to keep in mind. Since into_iter() consumes the data, we cannot use the original vector later. One solution would be to explicitly call v.iter() so that the iterator is borrowing instead. Another is to provide a reference to v rather than the owned value. This way, the compiler can no longer implicitly call into_iter() since it doesn't get an owned value. It gets an immutable reference, so the best it can do is implicitly call iter() on it - and that's what we want. Then there's mutability. Following the same pattern, here Rust will implicitly call iter_mut() and give us an iterator that is mutably borrowing. If all you could do with iterators was loop over them with the for keyword, they wouldn't be all that useful. But there is a plethora of adapters that transform an iterator into another kind of iterator, altering its behavior. Most adapters you'll work with are in the standard library and exist as methods provided for the implementers of the Iterator trait . There are some crates out there (such as itertools ) that provide extra adapters via extensions. We're going to go through a few useful adapters, but I highly recommend taking a look at the full list in Rust documentation . We can construct an infinite iterator using std::iter::repeat . It would be a bad idea to iterate over it directly. If we wanted to print only a few 1s, however, we can use the take adapter for that. What take does under a hood is wrap the Repeat iterator in a Take wrapper. Take is also an iterator, but this one finishes after returning a set number of elements. A very typical feature of functional programming is the ability to map , that is to apply a function to every element of a sequence. If we wanted to find only elements that fulfill a certain criterion, we can use the filter adapter. We know how to turn a collection into an iterator. How do we turn an iterator into a collection? Enter the collect method, provided by the Iterator trait. We could try to convert from a vector to an iterator and back. This, however, produces an error. What Rust is telling us here is that it doesn't know what we're trying to collect into. collect has a generic return type and could give you a number of things: a vector, a linked list, a string, etc. You could even create a custom type that can be collected into. To let Rust know what concrete type we want collect to return, we can use the turbofish syntax. We can make this slightly shorter. The compiler should be able to figure out that we want a vector of chars, specifically, and not a vector of integers. When filling out type parameters, we can use an underscore to tell Rust, "Figure this part out yourself!" In a case like this, you might find it tidier to add a type annotation to the variable declaration instead. The whole thing will then look like this: Using collect , we can convert an array of chars into a string. We could also collect an iterator of tuples (where the first element needs to be hashable) into a HashMap : Things that can be collected into implement the FromIterator trait. That means this behavior is extendable! Check out the trait's docs to see which types can be collected into and how to implement new ones. We now have some idea of how iterators work and some operations we can perform on them. Let's put it together and see some typical use cases. Let's say we store customer data in Customer structs, which include a customer's name, e-mail address, and how much they owe us. Then let's say we have a list of such customers. We're tasked with producing a vector of all debtor e-mails so we can send them a generic reminder. How do we do it? The nice, idiomatic way is to get an iterator over customers , apply some adapters that will filter and transform the data, and then collect that back into a vector. Given the same Customer struct as above, and the same vector of customers, we can search the customer data for a specific person. There's no useful method defined directly on the Vec<T> type, but there is a find method defined on the Iterator type. What if we'd like to get the position of an element in the vector? Things get a little trickier, but create an iterator. We then have to enumerate it to keep track of positions. enumerate will wrap every element in a tuple of formthe the (position, element) . Then we have to change our find closure a little to account for the items now being tuples. Finally, once we unwrap the Option<(usize, Customer)>, we still have to extract the position component of the tuple, which is the 0th one. The str type comes with a split method that yields an iterator over chunks of that string. All you have to provide is a pattern to split by - commonly a char , a &str or a String . For example, you could get all the words of a phrase this way: And then you could transform them and collect them into a new String : These are some examples of third-party libraries providing some functionality via iterators. This just about exhausts the core concepts and basic usage. Hopefully, you should now be able to not only effectively transform collections, but (with some practice) also identify where providing iterators would make sense in your code. One thing to do now would be to simply read the module-level documentation for std::iter , and take a look at the provided methods of the Iterator trait . There are some useful tools to discover there that we didn't cover here!

I got a job offer, thanks in a big part to your teaching. They sent a test as part of the interview process, and this was a huge help to implement my own Node server.

This has been a really good investment!

Advance your career with newline Pro.

Only $30 per month for unlimited access to over 60+ books, guides and courses!

Learn More

What Rust Is All About: A Small Tour

In this post, we'll try to give you a taste of Rust's key features so you may get a feel for the language. We'll touch on the type system, some handy functional features, traits, and more!Rust is a systems programming language that was conceived at Mozilla and is now developed by a larger community. Initially, its purpose was to help rewrite portions of Firefox's codebase (largely C/C++) to improve its safety and security. It has since enjoyed growing adoption in fields such as Rust is a statically typed language that emphasizes static code analysis, helpful compilation errors, convenient and comprehensive tooling, memory and concurrency safety, sheer performance, and abstractions that incur no (or little) runtime cost. Its memory model avoids the overhead of a garbage collector, but at the same time, the language's compiler enforces a set of rules that effectively prevent memory errors common in C/C++ applications. This is not a comprehensive "learn Rust in one hour" guide. It should, however, give you a little taste of what the language is about and help you decide if it's for you! We will guide you through some of the key concepts you'll inevitably stumble into in Rust code, including custom types, error handling, ownership, closures, higher-order functions, iterators, pattern matching, and traits. This guide is intended for people with some prior high-level programming experience, though which particular language(s) you've previously worked with shouldn't overly matter. If you'd like to give the snippets a run, the easiest way is to use the Rust Playground online. If you prefer to run things locally, the Getting Started chapter from the Book will help you get your environment and a "Hello, World" project set up! A comprehensive static type system means safer code and fewer manual tests that need to be written. The more information you can encode in the type system, the more Rust can check for free during compilation, allowing greater confidence in the correctness and reliability of your programs! Rust has structs. Structs have fields. Rust also has enums for when a thing can be either this or that. Enums have variants. An enum value can only be set to one variant. Enum variants can hold data. Both structs and enums can be generic and accept arbitrary concrete types inside. This is possible by declaring type variables in angle brackets ( <> ). In the example above, the compiler will deduce that foo is of type Wrapper<f64> . Wrapper<T> is not a dynamic type! It's resolved during compilation. Oh, and by the way, the let statement is how you declare a local variable. It accepts type annotations, but most of the time they're not necessary - Rust might be a disciplined and strongly-typed language, but it's fairly smart and able to infer types itself a lot of the time. You can add methods to any type you define using impl blocks. Rust doesn't have anything like a try...catch statement. Instead, there is the Result<T, E> type, defined more or less like so: Two types should be provided here. T is the type of the actual return value. E is the type of error data. A function that may fail should return a Result - like the one below that attempts to sum a vector, but fails if it's empty. The unit type () is often used to signify nothing is returned. In this case, we're basically saying we don't want to add any extra error information if the function fails - we just want to know that it failed. In "real" Rust code, it's generally a good idea to provide an error type that implements the std::error::Error trait, but that's outside of our scope right now. If we want to use our my_sum function now, we cannot ignore the fact it might produce an error. The snippet below won't work. This doesn't compile. The compiler tells us the result of summing is really a Result , and so cannot be added to a float. First, we need to unpack the value inside the Result and handle the potential error. One verbose way to handle the error (by crashing the whole application) looks like this: This match statement isn't some special construct for error-handling. It can be used for pattern-matching against any enum. We'll talk about it later. This is the power of Rust's approach to error-handling: there's no magic. You get to use the same control flow as you would for the rest of your code. There's of course shorthand for the above: Another strong point for Rust is that even if you don't use the Ok value (sometimes there is none), the compiler will still nudge you about having unchecked results. Forgetting about potential problems isn't easy. Unless we go into unsafe hackery, Rust doesn't have null values. Let's define a struct Foo. Any initialized variable of the type Foo must hold a valid Foo with a valid f64 number inside. Thanks to that, if you write a function that accepts Foo , you don't have to worry about handling a potentially null value. The compiler will make sure only valid, initialized data gets passed to the function. What if we want to express that a variable can hold nothing? This is one place where generic enums come in handy. The standard library defines this Option type: If we wrap our Foo in an Option , we can express that we might have a Foo or nothing. Every computer program has to manage the memory used for its data. When some data is no longer used, the memory it occupied should be freed. After it is freed, the program should never attempt to access that data again, or face bugs. Most modern languages use garbage collectors . Thanks to those, this de-allocation and proper use are not something the programmer needs to be concerned with. This, however, introduces some performance overhead. Unlike most high-level languages, Rust doesn't have a garbage collector. Instead, there's the model of ownership and borrow checking. Ownership is a key concept in Rust. The idea is that, at any time, every bit of data in memory has exactly one owner. When that owner goes out of scope, the data is tossed out. It is also possible to create references to owned data. These do not affect how long the data lives, but for your program to compile, the compiler has to ascertain that the references aren't used after the owner goes out of scope and the data is no more. If you try to compile the above snippet, you'll get this: Try to comment out the last println! statement and run the snippet then! By default, a reference is immutable. To create a mutable reference, you have to add the mut keyword like so: The first mut keyword allows mutation to the owned data at all. The &mut foo creates a mutable reference, which is then stored in foo_ref . *foo_ref dereferences the reference to get access to the underlying data and modify it through assignment. You can have many immutable references, but you can only have one mutable reference. While you hold a mutable reference, you cannot use the owner or have any other (mutable or not) reference. This is to prevent data races and mistakes. All illegal use of references causes compilation errors and forces you to fix your code before it ever has a chance to see production! One amazing thing about Rust is it brings some high-level abstractions inspired by cutting edge languages to the world of systems programming. Rust supports closures - anonymous functions capable of dynamically capturing the variables from the encompassing scope. These can be treated like any other value and assigned to variables. Those variables can then be called the same way regular functions can. Notice that num_printer prints two numbers, but they're passed through different mechanisms. The x argument has to be provided when calling num_printer , just like with named function. num_to_capture is implicitly captured by num_printer when the function is defined. This becomes much more interesting when combined with higher-order functions - functions that accept other functions as arguments and/or return a function. Say a third-party library defines a function called perform_calculations and allows you to extend the behavior of it by providing a callback. This is your chance to get partial results printed for the user currently logged in. Iterators are a major feature of Rust. They can be very convenient. Ranges of numbers are iterators. Iterators can be iterated on in a for loop. A borrowing iterator can be created for any collection, e.g. an array. It's possible to turn your own custom types into iterators by implementing the Iterator trait, too. An example can be found here ! There's one more way to execute something on every element of an iterator. What if we want to reverse an array, add an m to every element, and then collect it into a vector of strings? What if we want to get the product of numbers from 1 to 5 (not inclusive)? fold lets us apply a function to the first and second elements, then to the result and the third element, and so on. Hopefully, this gives you a taste of the power of iterators. They're not just for looping over them; they're a convenient and powerful tool for transforming data. The best way to learn about all those methods is to browse the Iterator documentation. That's also where you can learn to implement custom iterators - all you need to do is implement the trait on your own type. Rust allows pattern matching and all the de-structuring goodness associated with it. An underscore ( _ ) is used as a catch-all - it matches anything. Tuples can be de-structured while we're pattern-matching against them. Underscores can also be used inside those patterns. Here's an implementation of Fizz buzz : Enums can be neatly de-structured too. Instead of matching specific values inside of structures, they can be captured in a variable. Traits essentially declare some functionality that concrete types may provide. Traits can be compared to interfaces in other languages or (more accurately) to typeclasses in Haskell. They are Rust's solution to the problem of code reuse. They can pretty much act as abstract types, too. One important property that traits have and interfaces in other languages do not is that traits can be implemented for foreign types - even built-in primitives. It's entirely possible to extend f64 with your own, custom trait. One example of a useful trait is the Iterator one. It's fairly straightforward to create a custom iterator. The one below returns positive integers in order, ad infinitum. After we have one defined, we can use it like any other iterator, including all of those convenience functions mentioned earlier. They are provided "for free" for anything that implements the Iterator trait. Since many operators are either implemented using traits or defer to a trait when faced with custom types, operator overloading is possible by implementing those traits. It's usually a reasonably straightforward endeavor. Here's how addition can be implemented. Trait bounds are what make generic programming powerful. Previously, we mentioned type variables. It's possible to declare those for functions, too. There's not much we can safely do with a value of an unknown type, though. What if we knew something more about it? What if we knew what traits it implements? Here we define the function print_it generically for any value that implements the Display trait and can therefore be printed as a user-friendly string. Thanks to the trait bound, we know we can safely print whatever type we get. It's important to stress that this isn't dynamic - the concrete types are inferred during compilation. When you call print_it(123.3) , the compiler (rather than the runtime) will check that f64 implements the Display trait, and then essentially create a function from the generic print_it definition specifically for the f64 type. What does it matter if this check is performed during compilation or runtime? If we ensure the correctness of types we pass around during compilation, type errors never have a chance to make it to production. It's much easier to rely on this kind of mechanism than to write extensive tests for all the problems we may create. There's some preferable syntax sugar for the generic print_it function. It helps us avoid thinking about type variables. Life is usually simpler that way! Whew! Hopefully, this helped you get a feel for Rust and shown it as the clever language it is. We've taken a glimpse of the type system, error handling, safety, functional patterns, pattern matching, and traits. Again, this tour isn't comprehensive; we've only scratched the surface here. The path to understanding the language and writing idiomatic code is much longer. Every journey has to start somewhere, though. We can only hope this was a good start, and that it got you hungry for more. Want to learn Rust properly? "The Book" is widely considered the best course; we cannot recommend it enough. You will learn everything we've mentioned here and more. Some alternatives:

Prelude to Vectors in the Rust Programming Language

In this post, we are going to explore in detail how to work with resizable arrays in Rust. Specifically, we will take a closer look at the Vector type, its syntax, and some use cases like filtering and transforming a collection.In software development, we often face the need to deal with a list of objects or values. For example, enumerating words or ingesting series of numeric values, parsing structured data from tables or data storage like CSV files or a database. Also referred to as collections, such data structures serve as a container of discrete values, offering a great facility to organise all kinds of dynamic data in a program. The Rust Standard Library includes several different kinds of collections like static arrays, tuples, vectors, strings and hashmaps. Our focus here is set on one of the more commonly used array-like types - the Vector type. Unlike arrays and tuples, collections of type Vector are dynamic which means they can be changed at runtime, making them a versatile and convenient data type. In this post, we are going to explore the following aspects of the Vector type in Rust: Finally, we are going to take a moment to review how Rustā€™s internal safety mechanisms protect the developer from performing potentially unsafe operations. This tutorial assumes you are familiar with the Rust language syntax and have a general understanding of how a program allocates memory (e.g. heap vs stack). We often use terms like collections and arrays to describe structures of numbered lists of items. The vector type in Rust is one example of such a structure and it is the most commonly used form of collection. It has the type Vec , it is pronounced ā€œvectorā€. The basic structure of a vector can be seen as a combination of the following information: A vector can always be represented as a tripled of these 3 values: pointer, capacity, and length. Also, the basic nature of the Vec type allows us to make use of several guarantees provided by the Rust runtime. For example, the pointer of a vector can never be null as the Vector type is null pointer optimized. If a vector is empty (contains no elements) or all elements are zero-sized, then Rust ensures that no memory will be allocated for the vector. The capacity() of a vector indicates the number of elements that can be added to a vector without re-allocation of memory. It can be seen as a sort of reserved or pre-allocated memory. You can learn more about the guarantees and memory specifics of the vector type in the documentation . There are several ways we can define a new vector. A vector can be initialized using the Vec::new function which returns a new empty vector. Once created, the new vector variable can be marked as mutable (using the mut keyword) in order to be able to add and remove elements from it. Try it out for yourself. It is worth pointing out that we did not declare the type of elements we intend to add to the collection. Let's see what happens if we declare the vector as we did above, but don't insert any elements to it e.g.: This statement alone will not compile and the error message will be cannot infer type for type parameter "T" . This happens because the Vec type uses generics in order to specify the type of elements that will be added to the vector collection. In the first example, we added elements to the vector and so the Rust compiler was able to infer the type of the variable vec to be Vec<&str> as the elements being added to it are of type &str . In the second example, only initializing a new empty vector was not enough for the Rust compiler to determine what kind of elements we intend to store this causing raising a compiler error. In order to solve this, we can choose to explicitly specify the type of the vector collection during initialization. For example: The syntax of the Vec::new function may seem a bit verbose as we first need to initialize a mutable variable and only then add elements to it. Luckily, Rust also includes the vec! macro which adds certain facilities to make it easier to initialize new vectors by also providing the initial elements in the collection. We could rewrite our example in order to use the vec! macro instead of the Vec::new function as follows: Since the initial elements of the vector are known upfront, this can be made even more concise, by directly initializing the vector with the initial elements: Try it out for yourself. Now that we know how to create a vector, let's have a look at some of the techniques we can utilize in order to access the contents (or elements) of a vector. The length of the vector corresponds to the number of elements currently being stored by the vector. We can obtain the length using the len() function: The capacity of the vector is the number of elements that a vector can hold without the need to reallocate additional memory. A vector normally stores its elements in a memory buffer which can grow over time as new elements are being added to it. By default, the capacity of the vector is automatically adjusted as we add elements to the collection. When we create a new empty vector, we can choose to define an initial capacity, essentially reserving the initial buffer size of the vector. This means that when new elements are added, the vector will not have to reallocate additional memory as long as there is remaining space in its buffer (capacity). Let's illustrate with an example. Try it out for yourself. If we just create a new empty vector, it has 0 elements and a capacity of 0. This means adding an element will require the vector to first allocate some memory to increase its capacity to at least 1, in order to accommodate the incoming element. Of course, this works just fine and may not be a problem at all. For example, we can create a new empty vector with an initial capacity for 10 elements: The vector type in Rust implements the Index trait, allowing us to directly access elements by index: A common source of bugs and security vulnerabilities is what is commonly known as out of bounds access i.e. trying to access an element outside the length of a vector. While very easy to use, direct access by index has a downside - we may accidentally request an element index which is out of bounds which will cause the program to panic: To help with that, Rust offers an alternative using the Vec::get function which returns a value of type Option instead, allowing us to gracefully handle this scenario and as a result, improving the reliability of the program: Try it out for yourself. A mutable vector can be changed by adding or removing elements from it. We do this using the Vec::push and Vec::pop functions. Respectively they either append an element to the end of a vector or remove the last element of a vector. The Vec::pop method also returns an Option value which either holds the removed element or None if no elements were removed from the vector (e.g. when it was already empty). Try it out for yourself. In some situations it may be useful to update an element which already belongs to a vector: Try it out for yourself. You may notice that we are directly accessing an element by index. Like we saw earlier, given an index outside the bounds of the vector the application will panic. We can use a technique we showed earlier with the Vec::get method and its companion Vec::get_mut which returns a mutable reference to an element, if it exists. We can then rewrite the above example in a safer way as follows: Like get , the get_mut method returns an Option with a reference to the element at the given index. If the element doesn't exist (when the index is out of bounds), get_mut returns None . If the element exists, get_mut returns a mutable reference which we can use to update the value. If we would like to perform a certain operation over each element in a vector, we can iterate through all elements rather than accessing them one at a time. One way would be to use a for loop: In this case, we are consuming the vector by executing the operation defined in the for loop block over each element of that vector. We could also limit the operation to just references to the elements of the collection. Using the same technique, we can obtain a mutable reference to the elements, allowing us to affect changes to the collection: Try it out for yourself. Another powerful technique to access the elements of a vector is through means of an iterator. To obtain an iterator over a vector, we use the Vec::iter method: Try it out for yourself. Generally speaking, Rust makes it easy to use iterators for almost everything. In the ergonomics of the language, it is almost preferred to use iterators instead of directly interacting with a vector. An example use of iterators will be the case of transforming the values of a collection from one type to another. Given a collection of words, let's build a vector which holds the length of each word: Try it out for yourself. Iterators are a powerful concept in Rust and they prove to be very useful when we are interested in obtaining a subset of a given collection. We can use the Vec::filter method in order to filter the elements of a vector: Try it out for yourself. Nothing prevents our application from adding the same value multiple times to a vector. There are however circumstances when it may be needed to remove the duplicates from a collection. Imagine for example, if the vector is based on user-provided data and we are only interested in working with unique (non-repeatable) values. This is easy to achieve using the Vec::dedup method. Once called on an instance of a vector, dedup works on that same instance and removes consecutively repeated elements. This means that for the deduplication logic to work as we expect, the vector needs to be sorted so that repeating elements follow each other. For example [1, 3, 2, 3] will not work very well because the repeating values 3 are not adjacent to each other. Once the vector is sorted to [1, 2, 3, 3] we can make use of the Vec::dedup method in order to remove the repeating values. The Vec::deduce needs the elements of the vector to implement the PartialEr trait in order for the comparison to work. This means it can also work for custom structs, as long as they implement PartialEr . Let's check an example: Try it out for yourself. Here we declare the vector as mutable since Vec::dedup updates the contents of the collection in place. A common type to represent resizable arrays is the Vector type. It is one of the more versatile collection types, enabling a great deal of flexibility when accessing and working with its elements. In this post, we saw how to get started with using the vector type for common operations like filtering and transforming a collection of elements. In addition, we discussed some of the safety protections provided by the Rust runtime in such scenarios like guarding against out of bounds access or null pointers. You may also find it useful to explore the Vec specification from the Rust documentation where you can read about all available methods and additional sample use cases and code snippets. You can also check one of my other posts which cover additional use cases for using Iterators with vectors in Rust.

The Rust Map Function - A Gateway to Iterators

This post looks at the Map function in the Rust programming language as one of the basic tools to use and interact with collections and iterators.Arguably one of the main reasons the Rust language has become so popular is because the entire ecosystem builds upon a consistent experience. From toolchains to documentation and language specifications, almost every aspect of the language adheres to a certain level of consistency. Getting into the Rust mindset allows us, developers, to intuitively discover aspects of the language and gain the ability to write better and more consistent code. In this article, we are going to focus on Iterators - one of the key language tools enabling us to write idiomatic Rust code. This may surprise you at first, especially if you are coming from other programming languages (for example Swift or JavaScript), where working with types like lists and arrays does not immediately expose the concept of Iterators. The opposite is valid for Rust. Iterators cover a very large number of applications and as such, being comfortable with their use greatly facilitates becoming a better Rust developer. In order to follow along, you will need at least some familiarity with the Rust programming language. To make things easier, this article includes detailed code samples and links to the Rust playground where you can execute and study the provided code snippets. After reading this article, you will know: A core concept for Functional programming, Map is also commonly used as a tool for dealing with lists and collections in Rust and is one of the most essential methods for working with Iterators. The map method offers a way to apply a function (or a closure) on each element from a list. This is key in functional programming methodologies and offers a way to transform a collection of type A, to a collection of elements of type B. In Rust, the map method is frequently used to interact with vectors and any other types which can be represented as an Iterator . Let's illustrate this with an example. Imagine a list of numbers (a vector of numbers) and we would like to multiply each number by 10. Try it out for yourself. Let's review a breakdown of the steps You may be wondering why .map() doesn't work directly on the vector of numbers. The answer lies with the fact Rust makes it very easy to use iterators for almost everything. In the ergonomics of the language, it is generally preferred to use iterators instead of directly interacting with a vector. In the example above we actually have two iterators at play - the first which we obtain through the use of .iter() in order to get the elements of the vector and a second one which happens to be the return type of the .map() function! This may be a bit surprising at first, especially if you have a background in other programming languages (for example Swift or JavaScript) but indeed, in Rust .map() also returns an iterator. Let's have a closer look at the syntax we need in order to use Map. In the Rust documentation , we can find the exact signature of the Map method: With this we can focus on several key takeaways. .map() can only be applied to iterators. This means that we first need to convert a collection type to an iterator before we can transform its elements. To obtain an iterator from a vector, the standard library provides .iter() and .into_inter() . The return type of .map() is also an iterator. This can be very useful in cases when multiple transformations need to be chained one after the other. Let's illustrate this by extending the example with a second transformation of the vector of numbers. After multiplying by 10, let's divide each element by 3: Try it out for yourself. Since the Map function returns an Iterator, the result of the first transformation .map(|n| n * 10) will be used as input for the second transformation .map(|n| n / 3) . You probably notice by now that despite the return type of Map being Iterator, we somehow manage to get back a vector of results. This is possible thanks to the .collect() method. It acts as a consumer to the iterator, acquiring all elements and storing them into a vector. The Map function is lazy , meaning the execution of the closure provided to Map is delayed until the values produced by the Map iterator are actually requested. To demonstrate this, let's modify the example so that we count the number of times the closure of the map method is invoked: Every time .map() runs, it first increments the counter and then applies the transformation of multiplying the number by 10, similar to what we did in the previous example. As expected, the counter equals 4 because this is the total number of elements on which the map function was applied. Now let's modify this snippet by not calling .collect() at the end: Try it out for yourself. If we print the contents of the result variable, we see that what we get back is the actual Map iterator, the results of which are not materialized yet. We can prove this further by looking at the value of the counter variable which is now 0. This means the function inside the .map() has not been executed yet. The lazy nature of .map() gives us the opportunity to control when the transformation will be executed. You can imagine this can be significant in cases of large data sets where performance is important. Let's have a look at several examples in which the Map function can be a good solution. Try it out yourself. Starting from a vector of string values (or "words"), we can use the Map function in order to get the lowercased equivalent of each word. As before, we begin by accessing an iterator over the collection words. The .iter() method produces an iterator which yields every element (that is, every word) of the collection. Then we can use Map to define a closure which applies to_lowercase() on each element. In the end, we don't forget to .collect() the results into a new vector of String values. Let's define a string value and use Map in order to determine the number of occurrences of each character within the string. We expect an output giving the number of times each character is used, similar to the following: The result looks like a dictionary where every key is a character and the value of that key is the number of occurrences. Let's use the HashMap type to represent this value: Try it out for yourself. Here we use the chars method in order to obtain an iterator over every letter of the alphabet. Then we count how many times each letter matches the text input. For example, how many times "a" appears in "hello from rust!", then how many times "b" appears in "hello from rust!" etc. Finally, we collect the results from the mapping into a HashMap. So far we've used examples where the mapping function always succeeds. In reality, we have to take into account the circumstances when this is not going to be the case. Such cases include transformations that have an optional return type like Option or Result . Let's illustrate the case of using Map with an operation that can potentially fail. For example, converting a vector of string values to a vector of numeric values of type u32 . Applying what we've seen so far, we may attempt to solve the problem with the following snippet: Try it out for yourself. For each element of the vector, we use the parse method in order to convert the string value to a number. We realize quickly that this will not compile. The reason lies with the fact that Parse returns a Result<u32, std::num::ParseIntError> which indicates that the operation can either succeed returning a value of type u32 or fail with a ParseIntError . The map function is not equipped to deal with this situation directly. One way to solve the problem is to use .unwrap() , essentially forcing the fact that we are certain the parsing will never fail. If it does, the program will panic and exit: Try it out for yourself. This works because all string values in the source vector can be converted to a number. But what if we have a vector where some elements can't be expressed as a number? The unwrap fails and causes the program to panic. Luckily, the standard library provides an alternative that can handle the situation where the Map function can produce one or zero results. Meet flat_map() : Try it out for yourself! flat_map uses an iterator over the result of the mapping and as a consequence, it will skip over elements for which the mapping closure returns empty or unsuccessful values (like None or Err . In other words, we have a mechanism to skip over the failing elements and end up with a resulting vector of numbers for which the operation succeeded. A note of caution regarding side effects when using the Map function. Side effects refer to the fact that during the execution of a Map closure or a function, our program may modify state and variables which are external. We already saw an example of this, when we defined a counter which increments every time the Map function is executed: While this produces exactly the result we expect in this situation, it may not always be the case. Let's modify the example so that instead of transforming the numbers in the vector, we map the order in which they are processed: Try it out for yourself. This results in the Map function to first be applied to the number 3 on position 1, followed by the number 6 on position 2, etc. Since .map() returns an iterator, we could choose to modify its output. For example, let's reverse the order of the Map operation: Try it out for yourself. So the first number to be mapped was 12, followed by the number 9. The order in which the elements were mapped is no longer the same as the initial order in the vector. In order to ensure we have better control of operations with side effects, it may be preferred to use a for loop instead: Try it out for yourself. After seeing the Map function in action, we can outline several important takeaways. You may also find it useful to explore the Map function specification in the documentation which reveals all available methods and additional sample use cases and code snippets. When I need more information regarding an aspect of Rust, I find myself browsing the Rust Programming Language Book . I think it's an amazing resource for gaining further insight into concepts regarding Iterators, the Map method, or working with vectors. If you are curious about Iterators, you may want to further explore other helpful methods like .filter() and .fold() .

Your first Rust server with Rocket

This article guides you through the ins and outs of building your first Rust server. We're going to start from scratch and get to a server that greets you based on a route parameter.The Rust language has been gaining tracking for a lot of reasons in the last few years. One of them is the excellent performance and strong high-level constructs provided by Rust's zero-cost abstractions paradigm. Safety and efficiency are traits expected from all Rust frameworks. You might think that safety and efficiency is something you want from your next webserver application. You might also think Rust language, being lower level than our usual web server go-to languages, might be complicated or convoluted. That's where the developers of Rocket come in. They wanted to marry developer ergonomics to the benefits coming from the language itself so you have no choice but to choose Rocket as the web framework for your next project. We're starting from scratch, installing rust and rocket and building a server that greets you. The intention here is to introduce the Rocket framework for beginner Rust developers and show the door of web applications in the Rust language. First thing first: we need to get Rust running. I said we're starting from scratch and I meant it. To get started with our project, we need rustup and cargo. If you already have Rust installed in your system, you can skip this section. Rustup is the Rust toolchain manager. It's used to install and update the compiler and plug-ins like the official formatter and the official language server. Cargo is the Rust package manager, used to manage projects and dependencies. To get them both properly installed, follow these official instructions . These installers are very friendly to beginners and you should be done in no time. To verify it's all up and running type rustup --version and cargo --version . If you see their versions, we're good to go. Here's an example of my current outputs. Now having rustup and cargo working, we can start our project. Cargo has a handy method to get us started: cargo new --bin rusty-greeter . This command will create a new folder containing an executable project called rusty-greeter . Getting inside the project and running cargo run should execute the project, which will consist just of a Hello World print for now. Next, we're going to install the rocket package . If you're tired of all the setup, bear with me for just one more section. The project should contain an auto-generated Cargo.toml file. The required packages are declared there under the dependencies bracket. That means we want to add the rocket crate with the particular 0.4.4 version to our project. One more thing though: the rocket crate requires the nightly compiler to run, so we need to tweak one more option with the rustup override set nightly command. After that, we can run cargo build to see if it's all working fine. I removed the multiple "Downloaded" and "Compiling" lines from my output, but if you saw something like the above output you're ready to go! All right, the setup is done and solid. We want to have an example GET endpoint that sends from the server a "Hello World" string, just so we know it's all working. Our whole code will be written on the main.rs file, which should also have been auto-generated by cargo. Open up the file in our favorite editor and replace its contents with this. Let's break down this code, which is the core of the rocket application. This line is required by rocket to compile. It's a kind of declaration to the rust compiler what kind of experimental features we want to enable on our project. Don't think about this line too hard, it's just required by rocket currently so we do it and move on. This line just imports some functions from rocket to make our code cleaner. The first one, get , is a macro that defines a GET route. The second one, ignite , is the function that creates the rocket application instance. The last one, routes , is a macro that groups all our routes so they can be bundled into the application. Don't worry about understanding them all now, just pay attention to how they are used in the code. We're using the get macro along with a regular rust function to define, respectively, a route and a handler for it. This bit of code is what the route is and does: the request of a GET on the /greet path will return a "Hello, world!" string. Finally, the regular Rust main function is populated with the ignite , mount and launch function calls, which are just what rocket needs to start running. The important parts of this code are: the "/" string, which is the root path of the server; and routes macro, which should just list all routes in our server. We have all we need to see the first response from our server. To run the server with the current code, just run cargo run . This command builds the project if needed, so don't worry about building it yourself. After a successful compilation, you should see something like this. That's awesome! You can already see it's all running and with some emojis to make our day better. So how do we try it? You can just open up http://localhost:8000/greet in your browser or use an http client to try it from terminal. Here's an output from my favourite one. We already have the greet endpoint that greets the world, and all the setup that comes with it. We're half-way there now, we just need to take a parameter so we can greet you instead of the world. Thankfully for us, Rocket makes it easy to do so. Adding a router parameter to a route in Rocket requires no more imports than what we already have, we just have to pass an extra parameter to the get macro and take the parameter in the function handler like so. Let's once again break down the differences here. Where we previously had the /greet string literal, we now have a /greet/<name> . This just tells Rocket we are expecting something to follow the "greet" on the route path, and we want that something to be named "name". Then we take that "name" as a parameter on the handler, limiting to String s only. The Rocket framework will make sure only Strings pass into the handler. We also replaced the previous &str return type for a String one, which just means we're going to return a new entire string instead of a literal one. Lastly, this format macro call constructs the response we want from the parameter. This macro is very handy for simple string manipulation as we're doing right now. You are probably getting the hang of it already, but we need to stop running the previously running server (a control+C should suffice) and run it again. Unfortunately, Rocket does not provide us with an automatic code reload. After a new cargo run , we should see this again. Also again, we can hit it up on the browser at http://localhost:8000/greet/Lucas or with an http client, which will output something like this. You might be wondering so I'm just going to confirm it. I'm Lucas, that's why I tested with my name. I want to be greeted, just like you. If you're curious, you might have tried the previous example, without the /<name> parameter, only to find out we broke it. On the browser, it will show an error page. It's not found! This happened because we told Rocket that the name should be a String. Rust is very strict on its typing system so if told it's a String, it won't accept anything else. Since we want to support both the "Hello World" and the "Hello Lucas" paths, what should we do? It's surprisingly simple. If we want both behaviors to be present, you just have to write them both as two separate routes. That's what you're main.rs will look like when totally ready. There's a couple routes now, with both behaviours. They should have different function names, so Rust can tell them apart, and they should both be added to the routes macro. Re-running the server again with cargo run we can see they both are working! We made it, we have an up-and-running Rust server with a greeting route! Reaching here, you learned a couple of things. Of course, this is just an introductory post to servers in Rust. The intention here was to just break the ice and show that Rust can have good developer ergonomics even with higher-level applications, like webservers. Rocket paved the road and we walked it together. If you want to keep the learning going, I'd suggest you to start getting more complex with the webserver. Try to answer some of these questions while you keep learning Rocket and Rust. The Rocket documentation will be an invaluable help on your learning, but just by writing it yourself, you'll get familiar with the concept. I hope I have helped to get you started, thanks for the reading!

A gentle introduction to Results in Rust

This article will guide you through the ins and outs of the Result concept in the Rust programming language. Results are prevalent in Rust standard library and are a fundamental concept to grasp to become a prominent Rust developer.Here is how it's gonna go. Rust is a system programming language launched back in 2010 that has been gaining a lot of traction in the last few years. If the Stack Overflow survey is indicative of anything, is that more and more people are trying and loving it as their next pet programming language. In contrast, Rust is known to be a difficult language to learn and challenges even the most senior developers with their low-level constructs and new concepts. Coming from more popular languages like Ruby or JavaScript, one of the first stumps beginner Rust developers hit is on error handling. Rust statically types errors and treats them as any other data type, consolidating most of the error treatment code around the Result type . The concepts and treatments it gets are inherently different from most exception-based approaches, but there are also many parallels we can make. Let's walk through these similarities and differences! This article assumes no knowledge of the Result abstraction, which is precisely what we intend to cover next. It assumes general familiarity with the Exceptions error handling abstraction, but in case you haven't heard of it, you can still benefit from this article and maybe discover a couple ways of dealing with errors in your software. From the Rust language, some knowledge about the syntax is advised, but nothing further. So if you are familiar with simple Rust examples, you're good to go. According to the result's module documentation , a Result is a data type that is expected to be returned from functions that can fail in an expected and recoverable way. This is a short but insightful description of the usability and habitat of the Result, which points to the three main aspects to note about the Result abstraction. That's all they are, the return type of a function that is not guaranteed to work. So what is a function that is not guaranteed to work? Imagine you are making a function that divides two integers. If you get 6 and 2 as parameters, you know the answer should be 3. From this simple example you might be inclined to think your function's return type is an integer as well, but hold up. Can you guarantee that a division between two integers will always work? You probably already know where I'm getting at, but let's say you have a 6 and a 0 as inputs. What should be the answer in this case? More provocatively, what would be the integer that can work as the answer in this case? That's right, there is none because 6 divided by 0 is not an integer, it's a failure. So our integer division function wouldn't return an integer, but a Result that would wrap the integer, in case of success, or the explanation of why an integer could not be generated. The function is fallible, and therefore, returns a Result so the user can deal with its failure properly. Which leads us here. If we want to properly defined and type this function, we must know beforehand it can fail. We must know 6 divided by 0 cannot output an integer, so we can check against it and output the correct data type. This is also an important part of the Result usage: errors that cannot be anticipated, cannot be treated properly up the chain. An expected failure of an operation is part of its signature and will be properly annotated with a Result. Conversely, an unexpected failure cannot be annotated (because we don't know what it is yet) and it's handling will be impossible. Which finally leads here. Results are formally telling the caller of a function that it can fail so the caller is not surprised when it does. Not being surprised means having paths inside the program for when it happens. Common alternative paths involve using a default value or passing up the error, as we'll see in the next sections. One feature Results in Rust get, which makes them even more reliable, is that the compiler requires them to be treated . There are several ways they can be handled, but providing no handling code explicit will cause the compiler to raise a warning. I love this feature because it makes me fearless about the execution of my program when the compilation returns no warning flags for my code. If you're coming from languages like Ruby or JavaScript you might be thinking the definition we talked about earlier resembles Errors or Exceptions . If you made this connection, we are on track! Exceptions on these languages are abstraction created with very similar intentions as our Results here, but there are two crucial differences to note. Let's think back to our integer division example for a minute. The function returns a Result so it can account for the outcome in case of a division by zero. In case the division works properly the returned data type is still a result, representing the successful outcome. The Result is returned either way because it contains the operation outcome as a whole. Now let's think about Exceptions. We usually see them when something went wrong and only then. The successful path would return just the naked data type, the integer in our case. In other words, the function would yield the exception or the successful data type, differently from the Result, which is always returned containing the error or successful data type. You might have taken note of the "yield" verb in the last sentence. Why couldn't I have just used the "return" verb? An Exception is a special kind of data type on languages that implement it. They are generally seen together with verbs like "raise" and "throw", which are reserved keywords used to notify the runtime that something bad happened. Getting more technical, we could say an Exception is thrown, reversing the execution stack until it's properly dealt with. This means Exceptions are special to the runtime and have special keywords defined so they can be interacted with. Results, on the other hand, are just some data type you return from a function. Treating them like you would treat any other data type is the norm, which makes the cognitive load on the developers that use it lower. Further, being just a regular type, Results are part of every signature they are used in, so you'll never be surprised when it is returned. The compiler won't let you be surprised: it will tell you beforehand and make sure you handle it. I hope from this point we are all on board of the Result train. Let's figure out how to read it and use it in more practical scenarios then, shall we? The Result in Rust is defined as an enumeration of two options, with two generic types. One curious bit about this definition is that it's pretty common in many languages that have a Result implementation, like Elm and Swift . Many concepts read here transfer cleanly to those contexts. Here is the definition common directly from Rust's code. The two generic types, T and E, represent the type of the successful and failed outcomes respectively, to be contained by the Result. Each constructor carries one of these types, so we have a constructor for successes (Ok) and another from failures (Err). That's all there is to it. Getting more practical, our integer division example returns a Result<u32, String> , which means the operation can either be successful and return an integer or fail and return a string. The construction of a Success, as we have already seen, would be the successful number wrapped by an Ok constructor ( 6 / 2 = Ok(3) ). The failure, on the other hand, would be wrapped by an Err constructor ( 6 / 0 = Err("Division by zero!") ). Following the integer division, you might want to do another operation with the outcome. Let's say we want to divide 6 by 2, then double the result. We could naively try to multiply the Result by 2, which would promptly yield a compiler error. The error you see is just the compiler looking out for you. It is trying to let you know that you are assuming the operation that can fail will never fail. In other words, you are trying to multiply the successful outcome by 2, but what if the division had failed? How should the program react in case the division fails? Rust wants you to deal with it, so let's do it. In this case, we know the division can never fail since the inputs are the constants 6 and 2, which we know will result in Ok(3) . In these cases, we can tell Rust we are sure and positive the error will never happen. The unwrap function does exactly that: it removes the Result from the equation. You might be wondering, what happens if the outcome was a failure? In our case, the error would be associated with a string type, which cannot be multiplied by two. Further, even if it could, it might not make sense for a developer that is excepting the resulting division to be there. That's the danger and risk you take when you unwrap a Result. You, as a developer, are taking the responsibility and vowing the result will never be a failure. If it is, Rust will crash your whole program with a panic . At this point, nothing can stop the inevitable crash and burn. Scary, huh? So what to do if you are not sure the result is always positive, or if you just are not comfortable taking on this kind of risk? (I am always on the latter category). From the previous section, you might have gotten a feeling that unwrapping a Result is risky. Unwrapping is only safe if there is no way for the error to happen, which is unusual in more complex systems. In real-world programs, the inputs could be from user input, for example, which implies that we could not be sure if the division would work or not. For these cases, we would like to know if the operation succeeded or failed without risking the crash of the program. Rust has a powerful pattern matching implementation that works perfectly for us in this case. We could, for example, use it to default an error to a constant value, like zero for example. The match keyword lets us test against Result constructors and associate a path of execution to each. What this means is that we could get the successful integer, in case it succeeds, as long as we tell the program what to do in case it fails. The previous example shows how to provide a default value for a failure, but as we said before, we could also pass the error up the chain. Let's see what it looks like. Now we are passing the problem to the caller, just like the integer_divide function does. If the division fails, we don't know what to do, so we just return the Result. Note that in this case, we have to wrap the successful result in an Ok constructor so function signature is respected. This pattern of unwrapping or returning the result is so prevalent and useful, we Rust developers got tired of writing it every time. Rust has a macro that does just that for us . This looks better, doesn't it? The star of the show here is the ? operator. The funny thing about this example is that both the match and the ? are the same from the compiler's point of view. The macro and operator just make it easier on the eyes for us developers. Ok, things might have gotten a little intense there at the end. If you are here with me until this point, I know we already know your Result. Let's look back at what we've learned. If you enjoyed learning about this abstraction, you might want to read up on some of the other cool ones Rust has to offer. One of the best things about learning a new programming language is how it encourages you to look at problems another way. So where to go from here? Don't fear the Result and be safe out there, fellow rustaceans !