© Joseph Faisal Nusairat 2020
J. F. NusairatRust for the IoThttps://doi.org/10.1007/978-1-4842-5860-6_1

1. Introduction

Joseph Faisal Nusairat1 
(1)
Scottsdale, AZ, USA
 

The Internet of Things (IoT) is a highly encompassing term that covers everything from your home network-connected camera to the oven that is Wi-Fi connected, all the way to your modern electric cars like the Tesla that are always connected to the network and almost always on. The most basic premise of IoT is a hardware device that is a connected network appliance. In modern days, that usually means Internet and almost always connected to a cloud service, but it can just as easily be a local area network.

Only in the last 10 years have we truly embraced the IoT model for not only offices and factories but for everyday living. The most common consumer IoT systems are the ones from or supported by Apple, Google, and Amazon that provide cameras, thermostats, doorbell, and lights. All of those devices can then be used in conjunction with each other and for home automation and control. While many of these devices are used for fun in a home, they have beneficial application for elderly care and for medical monitoring and even can be used in industrial and manufacturing components. Devices in factories can report on the status of how many components are rolling off the assembly line, if there is a failure at a point, or even throughput of a factory. Used in conjunction with machine learning, the possibilities are endless.

And while IoT as a term didn’t make our way officially in the lexicon till 1999 by Kevin Ashton of Procter & Gamble, the concept has been around since well before that. What gave birth to IoT dates back to 1959 with the Egyptian-born Inventor Mohamed M. Atalla and Korean-born Dawon Kahng while working at Bell Labs in 1959. They created the MOSFET (metal-oxide-semiconductor field-effect transmitter) which is the basis for the semiconductor, which revolutionized electronics from huge tubes to the microchip components we have in our smart watches, phones, cameras, cars, and even your ovens. It would still take another 23 years though till someone at Carnegie Mellon decided to hook up monitoring a Coca-Cola machine for its inventory that would mark the first true IoT device, before anyone even thought of what IoT was, and then another 10 years before companies like Microsoft and Novell really proposed usable solutions. However, even then chips were expensive and relatively big. Today Raspberry Pi packs way more punch than the desktops of the 1990s, especially in the GPU department.

Who Is This Book For?

This book is for anyone from your hobbyist to someone trying to create their own commercial IoT products. I guess for your hobbyist, there is the question of why. Purchasing IoT applications has become inexpensive, fairly customizable, and routine; why bother to create your own? And while one answer is simply for fun, another is that you want to create a fully customizable solution. And finally another answer that became even more apparent this year was ownership, that you are the sole owner. This importance became obvious to me in two cases this year. This first was with the Amazon-owned company Ring. They had had to let go of four employees due to privacy concerns that they had spied on customers snooping in on their feeds. And while this is likely the exception and not the rule, it still lends to the idea of wanting to create pipes that are 100% solely owned by you.1 The second was Sonos, who after customers spent years buying components found out the older products will no longer be backward compatible, leaving many in the dark to use new software updates.2 And while it would be hard to replicate the amount of code they write, the open source community that integrates with custom Pi components is growing and will help to live on even if it means you have to code it yourself.

This book is titled Rust for the IoT. Before we discuss what we are going to build, let’s break out those two words further.

What Is IoT?

Internet of Things, or IoT, which will be used to reference it for the rest of the book, has become a new and ever-growing marketplace in the last few years, even though it’s been around for decades. At its core, it’s a network of devices that communicate with each other and often with the cloud.

The most common IoT systems are the ones from or supported by Apple, Google, and Amazon that provide cameras, thermostats, doorbell, and lights that all interact with each other. These devices in conjunction can be used in home automation and control. While many of these devices are used for fun in a home, they have beneficial application for elderly care and for medical monitoring and even can be used in industrial and manufacturing components.

In addition, IoT does not stop there; it’s gained dominance in all realms of device use. Car companies have started adopting IoT to have a more fully connected car. Tesla started the trend, and others have really picked it up full speed and use the same concepts and features as your smart device. Incidentally, this is something I know quite a bit about because I was in charge of architecting and coding Over-The-Air (OTA) updates for one such car company.

For this book though, I am sticking to personal use in the home, since most people into IoT are home enthusiasts and because creating one for a car is a tad more expensive since you would need a car. But the same principles can be applied everywhere.

I have had an interest in IoT since the rudimentary RF devices you could purchase in the 1980s from RadioShack. Quite a bit has changed since then. We are now in an age where home automation is actually pretty good. We have cameras, devices, cloud, and voice integration, but there are still many improvements to be made. We feel this book could start you on your way as a hobbyist or even in a professional setting. Why Rust? When reviewing what languages we could use for both embedded board development and was extremely fast cloud throughput computing at a low cost, Rust kept coming up.

IoT 10K Foot Picture

IoT at its core is the concept of connectivity, having everything interconnected with each other; executing that is often not the most simple concept. In Figure 1-1, I have diagramed your basic IoT interactive diagram.
../images/481443_1_En_1_Chapter/481443_1_En_1_Fig1_HTML.jpg
Figure 1-1

Showing your standard IoT diagram

Let’s get a few takeaways from this diagram. You will notice at the bottom there are a few hardware devices and a mobile application. Hardware devices in our case will be a Raspberry Pi, but they could just as easily be your Google Home Hub, Alexa, or a car. These are all devices you are familiar with. The Raspberry Pi and Google Home Hub in the picture serve as endpoints that can play music, capture video, or record other information about their surroundings. The mobile devices then serve a role in communicating with those devices (in the case of the Google Home Hub, it serves a dual role, one in communicating and the other in capturing the world around it).

The end goal as we said is to have a fully connected system, so not only do these devices communicate with the cloud, but they receive communications back from the cloud. The communication back from the cloud can be due to input from your mobile application or could be a scheduled call. The pipes between represent this communication, but you will notice we have a variety of communication paths listed.

HTTPS

This is your standard HTTPS path. These paths exist often from the device to the cloud. Remember the endpoint in your cloud will have a static domain name like rustfortheiot.xyz. This domain name allows a constant path for the IoT device to talk to. The device can upload video or other large data and can download video, music, and other media content. And it’s also available for anything that would require an immediate request/response, for example, if we wanted to know what the forecast was for today.

The downside to HTTPS connections is that if the server endpoints are down, if they are overloaded, they may be slow or not responsive at all. In addition, there is data that the device will send back that doesn’t require a response.

The hardware is the core feature the reason we even have the rest of the diagram. These will give life to our commands. A car every time you drive is generating data on your speed, distance, and so on. Your home devices know when you turn on the lights and when you walk by a camera even if it’s not recording; it’s detecting the motion. For that data, HTTP may not even work or is overkill.

Message Queue

Message queues (MQs) you have often used with any publication/subscriber system and that in many ways are a few of the use cases we just described. If you are sending health data of your device, periodical temperature readings, this is all pub/sub type systems. The device wants to send the data to the cloud, but it doesn’t care where the data eventually ends. MQs are battle tested to handle high loads and are not as often updated as your microservice updates. This means you can easily update your microservices without worrying about downtime of the application. In addition, if you need to take the microservice down for an extended time, you won’t lose the data; it will receive it once it reconnects to the message queue.

We will use the message queue as the intermediary for sending messages back and then the HTTPS call from the hardware for retrieving videos. Also remember that the calls you will be making for HTTPS will be secure connections, and the MQ calls should be via Transport Layer Security (TLS) . Now let’s jump to the cloud. You will notice a fairly standard application layer with microservices, a database, and a bucket to store files in. In our case, we will used a local store for saving image and video files. Two other interesting items are the message queue (MQ) and machine learning. Machine learning (ML) is growing and really helps with IoT devices since often they generate so much data. We just mentioned all the data that the MQ can retrieve. This data is invaluable in being able to use ML to generate guides, suggestions, and adaptive feedback. We won’t dive into machine learning in this book, that will be a book in of itself. If you are interested, you can read Practical Machine Learning with Rust (www.apress.com/us/book/9781484251201). The microservice architecture in the backend allows you to create a variety of small services you can scale independently of each other but can also communicate as if they are on one endpoint (we will discuss how to do this when we get to Chapter 7). These microservices can then talk to database, bucket stores (like S3), or the message queue. All of that backend will process data, serve as endpoints to route data from mobile to the device, and even send notifications either to the device or the mobile application.

Why Rust?

The next question that may come to mind is why did we pick Rust? If you look at most web applications, they aren’t written in Rust; if you look at most board development, it isn’t in Rust either. So why Rust? Rust is a multi-paradigm programming language that focuses on performance and safety. Rust, by what it allows you to do, has quite a bit more performance and safety implemented than other languages. The biggest way this is shown is in Rust’s borrowing and ownership checks. Rust makes it so that there are specific rules around when a variable is borrowed, who owns it, and for how long they own it for. This has been the main attraction of Rust for many. The code becomes faster, less memory intensive, and less like to have two variables access each other at the same time. We will get into this more in the borrowing section. Stylistically, Rust is similar to languages like Go and has C-like syntax with pointers and references. And while some of the Rust crates lack the maturity of other languages, the language itself is continuously enhancing and added to.

Pre-requisites

While we are covering some Rust syntax at the end of this chapter, this is not an introduction to Rust. And while I don’t think you need an advanced understanding of Rust, if you are familiar with other functional or imperative languages, then you should be able to get away with a basic understanding. However, if you don’t have that background and have already purchased this book or are thinking of doing so not to fret, I’d suggest one of two options:
  1. 1.

    Learn Rust (www.rust-lang.org/learn) – Between reading the online book and the examples, you can gain quite a good understanding of book. The book is often updated and usually up to date. It’s how I initially learned Rust.

     
  2. 2.

    Beginning Rust book (www.apress.com/gp/book/9781484234679) – Often it’s easier to learn through longer books that will go into greater detail, and if that’s what you need, Beginning Rust is for you.

     

In addition, throughout the book we are going to cover a number of topics from microservices, GraphQL, CQRS, Kubernetes, Docker, and more. And while I will provide some explanations and backgrounds for each technology we introduce, there are entire books devoted to each of those tools. If you ever feel you need to learn more, I would suggest looking online; we will give resource links during those chapters.

Components to Purchase

In the first half of this book, we will create a cloud application, and while we will be deploying that application to DigitalOcean cloud services, that isn’t actually a requirement in building everything. Even then, we are picking DigitalOcean over services like AWS to mitigate the cost.

However, the second half of the book does deal with creating a Raspberry Pi–based application with a few add-ons. And while you will be able to follow along with this book without any cloud or hardware, to make the most of it, we will recommend a few cloud pieces and hardware that is designed to integrate with the software in this book. In the following section, I’ve given a list of hardware that you will need to purchase to fully follow along with the book. I’ve also provided the links on Amazon, but you can get the actual hardware from anywhere, and after some time the links may change:
  1. 1.

    Raspberry Pi 4 4 GB Starter Kit (https://amzn.com/B07V5JTMV9) – This kit will run about $100 but will include everything we need to get the basic Raspberry Pi up and running: from cables to connect it to your monitor, to power cables, and even a 32 GB SD card to be used for application and video storage. There are cheaper kits you can buy, but the all-in-ones will be a great starting point. Note: I selected and used the 4 for development, but if you used a 3, it should work as well; you will just have to adjust some endpoints when downloading OS software. The full Pi 4 kit will cost roughly $100.

     
  2. 2.

    Raspberry Pi Debug Cable (https://amzn.com/B00QT7LQ88) – This is a less than $10 cable that you can use to serially connect your Pi to your laptop without having to have a monitor, keyboard, or SSH ready. We will use this for initial setup, but if you are willing to hook your keyboard and monitor directly, it’s not necessary.

     
  3. 3.

    Sense HAT (https://amzn.com/B014HDG74S) – The Sense HAT is an all-in-one unit that sits on all the Pi’s GPIO pins that provides an 8 x 8 LED matrix display as well as numerous accelerometer, gyroscope, pressure, humidity, temperature sensors, and a joystick. We will be making use of the temperature, LED, and joystick later in this book. But this HAT provides quite a bit for $35.

     
  4. 4.

    Raspberry Pi Camera Module with 15 Pin Ribbon (https://amzn.com/B07JPLV5K1) – The camera we will be using is a $10 simple camera that is connected by ribbon to the Raspberry Pi. Since we are using this for simple video and face detection, the camera can be fairly basic, but it’s up to you how much you want to spend on it.

     

While I have given you Amazon links to purchase everything, you are free to purchase from any supplier; it’s all the same. This was just for ease of use.

We will cover and use all these components throughout the book.

Goals

The main goal of this book is to create a complete IoT application from the device all the way to the backend and all the parts in between. Without taking too many shortcuts, we will be using practices and techniques that are used for larger-scale applications. The goal is to give you all the lessons needed should you wish to expand your IoT application.

What we will actually be building is a HomeKit-enabled video recording device that stores and parses for metadata video and image files to the cloud and allow downloading and commenting on those videos. Here are the details:

Raspberry Pi – Allow a user to authenticate so that we know which Pi the files originate from. Allow the user to click a button on the Pi to see the temperature. Record video with facial recognition storing the video and image captures and sending the data to the cloud.

Cloud – Allow downloading and uploading of video and image files. Parse video and image files for metadata content. Create endpoints in the backend for users to create comments and query comments for the video files.

To perform all these features, we will use dozens of rust crates all working together to create a seamless system. We will create an application using a variety of tools like GraphQL, OpenCV, and eventual consistency (EC), all words that will become more clear as we go on. I will say what I am writing is not anything you couldn’t figure out yourself if you know what to look for. Most of this information is available online, if you dig far enough, but it’s sometimes hard to pick the right crates and get them to work together, and we’ve spent countless hours researching for our own work and for the book to bring it together. And in many instances, I’ve forked crates to either upgrade them for our use or to provide customizations we need. The code for the book will cover in more detail the following techniques:
  1. 1.
    Server side
    1. a.

      Creating a deployable set of microservices

       
    2. b.

      Server application that exposes GraphQL endpoints

       
    3. c.

      Server application that uploads and downloads files

       
    4. d.

      Communicating with hardware securely via MQTT

       
    5. e.

      Creating and using certificates

       
    6. f.

      Creating Docker, Helm, and Kubernetes scripts to deploy the application

       
     
  2. 2.
    Hardware side
    1. a.

      Setting up a Raspberry Pi

       
    2. b.

      Adding peripherals to the development board/Raspberry Pi

       
    3. c.

      Interacting with HomeKit

       
    4. d.

      Capturing video data

       
    1. i.

      Performing OpenCV on the video

       
    2. ii.

      Recording and uploading video content

       
    3. iii.

      Using SQLite to have a resilient store of data

       
     

Before we start coding, we are going to discuss the server and hardware side more.

Source Code

All of the source code for this book will be located on my GitHub page at http://github.com/nusairat/rustfortheiot.

This will include the services for the cloud, the applications for the Raspberry Pi, and the necessary build-and-deploy docker files. While I do step you through most of the code in the book, some of the more repetitive items like arguments for variables I only show you once to apply to your other services/applications. If you have any issues, please create an issue or you can tweet me at @nusairat. Now of course be aware that as the years go on, there could be compilation issues due to the version of Rust that is the current version. As of the time of finishing this book, the version of Rust is 1.43.1.

Web Application

The first half of the book will be on the server side. On that side, we will create a multitude of endpoints and tools that work with each other. The following is what we are going to make:
  1. 1.
    Microservices
    1. a.

      Upload/download service

       
    2. b.

      Retrieval service

       
    3. c.

      MQ service

       
     
  2. 2.

    Postgres database

     
  3. 3.

    Eventstore database

     
  4. 4.

    Message queue

     
All of those services will become more clear later. The first backend server we are going to design is going to serve a multitude of purposes, but essentially act as a bridge between your IoT device and the cloud. This will allow us a multiple of flexible options that you may not get with having a stand-alone IoT application and certainly is the way that most home devices work these days:
  1. 1.

    Act as a remote storage. When recording video or images from your IoT device, we will store it locally on the IoT device initially. However, this is not ideal if we want to retrieve the data from a remote client device; the round trip would be extremely slow. In that case, the application would have to call a server, and the server would then have to call the IoT device and start the transfer of data. While this is fine for real-time live video, if you are trying to view lots of historical archives, the slow download speed would become uncomfortably noticeable. In addition, it’s great to have offsite storage of the data to serve as backup for your application. Most cloud providers will provide fairly cheap storage for large data; they just charge you for access to the data. As long as you aren’t constantly accessing the data, you are fine moneywise. Our cloud application will be able to store to the local file store of the server it’s on as well as to cloud storage services like S3. The reason for this will become apparent later, but this will allow us to run one of the upload services locally from a Raspberry Pi (or other server) co-located in your house and to the cloud. This can help lower costs for storage and servers and is common in any do-it-yourself system.

     
  2. 2.

    Another issue we want to tackle is sending commands to the IoT device. Mobile devices allow you to send commands to your home units through the backend. In our application, we are going to allow recording start and stop commands to be sent via a RESTful endpoint to the backend that will control whether the Pi records or not.

     
  3. 3.

    Querying of data. As you store more and more files, images, and video, you are going to want to add tags to these uploads but also search for them, not only custom tags but the metadata associated with the files. Images and video often have metadata created and stored with them. These can include things like location, time, aperture and other settings (for video/camera), quality rate, and so on. These are all services the user will want to search for. We will parse the video and image files and store their metadata for use later.

     

Board Application

When we first started thinking of what we wanted to use with Rust, the board is what attracted us the most. With the board, there are many options from your Raspberry Pi to a more advanced iMX.8 board, which we initially thought of going for, but then the Raspberry Pi 4 came out. The 4 is an extremely powerful and advanced board, and it is not only a hobbyist board of choice but is often used in the real world. In the real world, before you’ve created your custom chipset, design, and others for your hardware piece, the Pi can serve a short-term prototyping tool that your engineers can work on while waiting for revisions of the production board. Raspberry Pis are the hobbyist choice because of their cost, size, and ability. There are a few ways of creating IoT solutions, and companies employ a variety of solutions. You have anywhere that range from relatively dumb devices that do one thing like take record temperature and send it back to a common device (think of Ecobee’s thermostat sensors) to a more encompassing device that has speakers and cameras like a a smart doorbell, or even more advance that has monitors, sensors, and so on. With the Pi, our options are a bit more limitless since we can attach whatever sensors we want to the Pi. In addition for something that is just recording temperature, you could go down to a cheaper Pi Zero. All of this will be future options for your components; for now, we are sticking with one Pi that has all the components attached to itself.

With this setup, we are going to be able to record video via attached camera, have display interactions, and show the temperature. The purpose is to give you a powerful starting point for creating your own applications. You will interact with the GPIO and the camera port and learn how to build and deploy applications to the board. One of the biggest hurdles will be how to run multiple processes at once in Rust on the board to perform heartbeats, face monitoring, and receive input.

The set of applications we will build for the board are as follows:
  1. 1.
    Face recognition video recording
    1. a.

      Background uploading of video

       
     
  2. 2.
    Communicate with MQTT
    1. a.

      Send heartbeat

       
    2. b.

      Receive recording commands

       
     
  3. 3.
    LED display
    1. a.

      Display pictures for holidays

       
    2. b.

      Display device code for authentication

       
    3. c.

      Display temperature

       
     
  4. 4.
    Homekit integration
    1. a.

      Display temperature

       
    2. b.

      Display motion detection

       
     

Basic Rust

While I mentioned a few other resources for learning Rust, I feel I’d be remiss if I did not at least cover a basic introduction and touched on topics and language syntax that you will see throughout the book. As software developers, and especially modern-day software developers, switching languages is part of our everyday job; as a community, we keep evolving to solve new problems. In this final section of Chapter 1, we are going to discuss the Rust language, its syntax, and its features and go over some code samples which will help you understand the language. If you already are comfortable with Rust, you can skip this section and start Chapter 2; if not, read on.

Rust Origins

Rust is not a new language but rather has been around since 2008 but until recently got popular in the main stream. It was started by and still the biggest contributors to it are Mozilla. It was mainly used as a language for the Mozilla browser engine. Rust syntactically is like C/C++ with the standard curly brackets and language syntax. However, it is not the class-based inheritance-type language you find with C/C++ or Java; instead, it’s a very functional language. The main focus of rust was speed and memory safety. And it exceeds in both; it is extremely fast beating, even Golang in many tests as well as C and C++. Memory management like we discussed earlier is very competent without you having to micromanage the application. So what are the places to use Rust? Well, here are four significant areas for Rust.

Command Line

Rust has great support for command-line tools and can run quickly and start up fast. There are tools to make it easily distributable as a binary to others (we will be creating these later). You can also create a robust set of configurations to make it work in multiple environments, have first world logging, and can talk to about any set of data points. The final item, the data points, makes it very good as a cloud-based middleman service. If you need a service that monitors webhooks, databases, and message queues, it can serve in a low CPU/memory pod funneling and processing data between other systems all the while not exposing NodePorts or easily attack vectors into the pod.

Modules

For existing or even for more full-featured applications, there are often slow components or performance critical components for it. Rust can be used in many languages to perform high-throughput processing of data, JSON, and so on. WebAssembly is a commonly used for this to create performance critical JavaScript and wrapped in a module.

Microservices

While not traditionally designed for web applications, microservice architecture is a great use of Rust. This often fills the same space that Golang and other languages try to create. Rust’s speed and memory management lends it well to microservices that are accessed repeatedly but where you may fear memory growing out of control. Much like Golang, Rust can use Docker scratch containers (more on this later) that will allow you to deploy an application with low memory footprint. This, for anyone deploying to the cloud, will save money in the long run; this is one of the biggest reasons shops are switching from the traditional JVM model to the scratch container model.

Embedded

Embedded is where the biggest need for a next-generation language really relies. For decades now, C and C++ have been the language of choice for embedded designs. Because of this, there are already many libraries written in C for working with embedded systems. To help this, there is some shared interoperability between the languages, including Rust containing C-type data structures that can be used in your code. We will go over these later in the chapter. The two biggest reasons is that it’s compiled down, so it can be run without an interpreter, and its memory-safe by writing your own memory allocation and deallocation. However, Rust really helps to solve both problems; it’s compiled to byte code, and the memory management has rules that it forces you to live by making the memory allocation predictable and repeatable. Rust also makes it impossible to perform accidental concurrency.

So of those four areas, we will cover in this book the command line, microservices, and embedded.

Learning Rust

This book at its heart is not a “learning rust” book; it is more a book on systems engineering of an IoT system with Rust. You will need to know at least some Rust to understand the examples. If you haven’t learned it yet, there are many great resources to learn the Rust language. Probably the easiest and best is the Rust Programming Language written by Nicholas Matsakis and Aaron Turon; it’s an online book you can download and use (https://doc.rust-lang.org/book/index.html). Or if you want more details, Carlo Milanesi has Beginning Rust: From Novice to Professional that is also useful. I’ve also read the Rust in Action MEAP, and that book goes into some very deep concepts and base coding that can be fascinating as well, but I don’t think it’s necessarily a book for beginners.

Now this being said, if you are like us, picked up the book and wanted to dive in as fast as you can, then this next part is for you. We will go over many of the basic concepts of Rust and the syntax and rules for the language including many of the features we are going to use in the book. This section is obviously much shorter than a book or a site dedicated but should help you at least get your feet wet enough to be able to read many of these examples of the book. This is far from comprehensive but should get you going enough to be dangerous.

Let us dive in with what the most basic Rust application looks like, and we can build up the application from there. So let’s start with the traditional Hello World application.

Installing Rust

First up in order to use Rust, you will have to install rust. While this seems straightforward , my experience has been it is not as straightforward as it usually is. Often when I am installing a new application (and note I am on a Mac), I just go to Homebrew and install the new tool set, be it Clojure, Python, Golang, or Elixir. With Rust, you don’t necessarily want to go that route.

Do not use Homebrew to install Rust directly.

What you want to install is rustup; this is the system installer for Rust. Part of the reason for this is you do not want to install just Rust, you are going to want to install a few other items that will make your life easier:

Cargo – This is the package manager for Rust we will be using throughout the book and our examples to run Rust applications.

RLS (Rust Language Server) – While you can run Rust from the command line without RLS, you will want RLS if you plan to use VS Code and other editors. RLS will allow rust to compile and run from your editors.

There are two ways to install rustup; there is a Homebrew script you can install called rustup-init that then runs the rustup initializer. I do NOT recommend this way. It really doesn’t buy you anything. Most direct is in Listing 1-1 where we run the curl command at https://rustup.rs/: curl https://sh.rustup.rs -sSf | sh. This will install Rust and Cargo for you. Once installed, you can run rustup -V to make sure you have it properly installed; it should have a version of at least 1.16 or higher.
➜ curl https://sh.rustup.rs -sSf | sh
➜ rustup-Vrustup 1.16.0 (beab5ac2b 2018-12-06)
➜ cargo -V
cargo 1.34.0-nightly (4e74e2fc0 2019-02-02)
Listing 1-1

Installing rustup

Now that you have Rust installed, we can move on and start coding in Rust.

Hello World

We shall start our tutorial with the quintessential example that every software book will have (or at least some variation of it). Listing 1-2 shows the Hello World example.
fn main() {
    println!("Hello, world!");
}
Listing 1-2

main.rs, the most basic application

The first thing you may notice is the file extension; we use an .rs extension for our Rust language files. The next thing you notice is the function definition fn main(); every application you create will start with a main function in a file called main.rs. In addition, you will notice the semicolon at the end; this is required (kind of, but we will get into that later). Also what is called the println! is actually a call to a macro, not a function. This is your most basic method that you can create and run, printing out Hello World. For most of our applications, we will compile via cargo since we will be relying on dependencies and multiple file interactions. However, for one file, we can use rustc, the rust compiler to build and execute the application. When you compile with rustc, it will create a binary file that can be then run without any interpreter or other system files.
➜ rustc main.rs
➜ ./main
Hello, world!
Listing 1-3

Commands to compile and run the application

OK, so now that we have the customary introduction to every language, let’s start diving into doing things more than println.

Writing in Rust

A theme you will see in this book and in general talking about Rust is that it has C/C++-like syntax and the memory management, so consequently those are the two major things we will be discussing and showing. Much of this chapter is showing you the syntax and the features; for those of you who want to dive all in to Rust, let’s get to it.

Variables and Data Types

We showed the most basic Hello World example; now let’s in Listing 1-4 show the basics of programming: creating variables and setting them to particular data types. Creating a variable in Rust is extremely simple; all you need to do is use the keyword let followed by the variable name set equal to a value.
let a = 3;
let b = 'J';
let c = "Joseph";
Listing 1-4

Setting values three different ways

This is about as simple declaration as you can get. Each line creates a variable, setting it to a different type:
  1. 1.

    The first line is setting a to the number 3; by default, this will be set to a medium size number type of u32 (more about that in a bit).

     
  2. 2.

    The second line should be familiar to most; the single line denotes setting a char.

     
  3. 3.

    Finally, the third line is setting what looks to be a String, like we have in most languages. This however is not; it is what’s called a string slice. And in fact, it has a static lifetime, which when you think about, makes sense since we have hard-coded the string on the right; it isn’t a variable that will get changed.

     
Tip

It will be important to understand the difference between string slices and Strings and when to use each.

While this is an acceptable way of creating variables, the preferred way is to define the type when creating the variable like we have in Listing 1-5. This is more readable for anyone coming into the application; in addition, it will help your IDE in performing code completion. Rust and many dynamic languages are interesting in their ability to allow optionals, but generally it’s best to define it. Also with types, you can define your variables without initializing them (you can’t do that without a type since it would have no clue what type to create).
let v :u32 = 3;
let x :char = 'J';
let y :&str = "Joseph";
let z :i64;
Listing 1-5

Setting values three different ways with types

The previous listings all follow with the corresponding types; I added a fourth one showing how you could create a blank variable. However, the way it’s currently written is rather useless since you can’t set any of these values once initialized. If you are familiar with other languages like Java, they wrap various number types in things like integer, double, and float which require you to then remember their size. In Rust, as you can see, it’s more upfront so that you have an idea of the amount of bits you are allocating for the data type. One other difference is every type can have a signed and unsigned version; this lets you allocate smaller sizes for bigger numbers if you do not need the negative versions. I’ve marked here the various types you can use as well as a C++ and Java equivalent to help if you are coming from either language.

Type

Signed

Unsigned

C++ Equivalent

Java Equivalent

8-bit

i8

u8

int8_t/unit8_t

byte

16-bit

i16

u16

short int

short

32-bit

i32

u32

int/float

int

64-bit

i64

u64

double/long double

long/double

128-bit

i128

u128

custom

BigInteger

arch

N/A

N/A

isize

usize

  • Note: The 8-bit one in C++ is an unsigned char.

Changing the Variable

In order to mark a variable that we want to change, in Listing 1-6 we have to apply the mut before the variable and then we can set it.
let mut x = 3; ①
x = 5;
let mut y :i32; ②
y = 25;
let mut z :i32 = 3; ③
z = 2;
println!("X : {} / Y: {} / Z: {}",x ,y, z);
Listing 1-6

Mutation example

  • ① Changing a value of a variable you have set.

  • ② Changing a value of a variable you have not set; in this case, you HAVE to set the type.

  • ③ Changing a value of a variable whose type has also been set.

Changing Value That You Have Passed In

There are also other types you can define and use. In Listing 1-7 we have examples of those types.

Other types are as follows:

Type

Sample

bool

true/false

char

'a'/'1F63B','U+10FFFF'

array

 

slice

 

str

 

tuple

(i32, f64)

let x :bool = true;
let y: char = 'u{00e9}';
let z: char = 'a';
let a: char = '1F63B';
println!("Extra Chars: {} , {}, {}, {} ", x, y, z, a);
Listing 1-7

Example of using a variety of types

Borrowing and Ownership

We mentioned the borrowing and ownership earlier, and this is one of the keys to the Rust language. And honestly, this will be the hardest part to wrap your head around when coming from other languages that have very loose borrowing rules. When you first start writing Rust code, you will probably get quite a bit of borrow checker errors on compilation; this is normal, and the more advance of a feature, the better it will be. I still get the same problems, albeit it’s usually due to the library I am using.

But let’s go over two quick examples, one where we reassign a variable and one that doesn’t work. In Listing 1-8, we set a variable and then reset it to another variable.
let zed = "4";
let me = zed;
println!("Zed : {}", zed);
Listing 1-8

Setting a variable and then changing the contents to another variable

This will compile and run and print “Zed : 4”. Let’s try this again, but this time in Listing 1-9, we will use a struct.
#[derive(Debug)]
struct Number {
    num: i32
}
fn run() {
    let n = Number { num : 25i32};
    let mv = n;
    println!("Number : {:?}", n);
}
Listing 1-9

Setting a variable and then changing the contents to another variable but with a struct

This is basically doing the same thing as the previous, but this one will fail with a “value borrowed here after move” for the println. Why is this? Let’s break down what’s happening. On the first line of the method, n is initialing the Number to a section of memory. On the second line, that memory is now pointed to mv instead of n. Since only one section of memory has a variable pointed to it, n now has nothing pointed. This makes sense but begs the question why did the first set of code work, but this code fail. That is because in the first set it was actually performing a copy of the memory instead. Thus, zed and me were both pointing to different memory both holding “4”. We can actually do the same with the second example by adding Copy and Clone to the derive; this will allow the compiler to automatically have in the second reference to perform a copy task instead. You will see that the borrowing and reference are all done via compiler optimization as well, making it much faster.

In addition, the same concepts apply when passing a variable to a method. When passing a variable to a method, we let the method borrow the variable, and won’t be able to use is it after the method returns, since the method has taken ownership of the variable and is now considered the final owner. In Listing 1-10, we borrow passing a u32; as you can see, this works because it performs a copy.
fn create() {
    let x :u32 = 3;
    copy_var_to_method(x);
    println!("X :: {}", x);
}
fn copy_var_to_method(x :u32) {
    println!("x: {}", x);
}
Listing 1-10

Passing a u32 to a method

However, much like the previous example in Listing 1-11, a String which does not implement Copy tries to perform the same sequence, but it would fail (hence, why I have the println commented out).
fn create_str() {
    let x :String = String::from("Joseph");
    take_ownership_str(x);
    //println!("This would fail : {} ", x);
}
fn take_ownership_str(y :String) {
    println!("x: {}", y);
}
Listing 1-11

Passing a String to a method

However, just because we pass the ownership to a function, it doesn’t mean we can’t pass the ownership back to the calling function. In Listing 1-12, we pass the ownership to the function and then return it back to the same variable.
fn create_str_and_move() {
    let mut x :String = String::from("Joseph");
    x = take_ownership_str_and_return(x);
    println!("End of method : {} ", x);
}
fn take_ownership_str_and_return(y :String) -> String {
    println!("x: {}", y);
    y
}
Listing 1-12

Passing ownership to a method and then returning it

Note you do have to make the variable mutable to set it; if not, you would have received a “cannot assign twice to immutable variable” had you tried to reassign x.

Memory

When talking about borrowing, it’s also useful to talk about the lifetime of the memory associated with it. Memory in any system is finite, and if you’ve ever worked with a large or heavy use application, you’ve probably run into some memory issues. Much of this is simply because the way other systems handle memory isn’t optimal. When we talk about C, we would use alloc and dealloc to allocate and deallocate the memory accordingly. This is a manual process, but gives the developer great flexibility in controlling memory usage. However, I’ve seen quite a few times this also leads to memory leaks due to programmer error. Java decided to take this out of the programmer’s hand and used garbage collection to clean up memory. Periodically, Java will run a garbage collector to free up what it considers memory no longer in use. This works great most of the time; however, this gives the developer no ability to control memory cleanup and in heavy use or an application creating quite a bit of objects no ability to control its cleanup.

Rust, as we said, uses borrowing to know when the memory is being created and also when it no longer has owner to it (like when you pass a variable to a method and then return from the method). Therefore, Rust at compile time can control when memory is allocated and when it’s deallocated by using the rules of its borrowing and ownership to know when a variable is no longer have an active pointer to it. To that avail, you can see this happen if you implement the destructor on a struct. Destructors work by implementing the Drop trait. In Listing 1-13, I implement the Drop trait for a person.
#[derive(Debug)]
struct Person {
    name: String
}
fn run_outofscope() {
    let p = Person { name: "Joseph".to_string()}; ②
    move_memory(p);
    println!("Finished");
}
fn move_memory(p: Person) {
    println!("Person: {:?}", p); ③
}
impl Drop for Person { ①
    fn drop(&mut self) {
        println!("Dropping!"); ④
    }
}
Listing 1-13

Implementing the Drop trait for Person

  • ① Implement Drop trait on Person.

  • ② Instantiate a Person and create the memory for it.

  • ③ At this point, the variable will go out of memory.

  • ④ Destructor is called.

If you run this set of code, you will get this:
Person: Person { name: "Joseph" }
Dropping!
Finished

As you can see, the destructor gets called once we return from the function. Since most of Rust’s memory is by default allocated to the stack as well, the memory space is now free to be used by any other item initialized down the chain.

Reference and Dereferencing

Instead of passing variables for complete ownership transfer, we can send references of the variable to the function. This allows us to still use the variable when the method is complete. When you do pass a variable though and want to alter it, you will have to dereference the variable to set the memory. In Listing 1-14, we have an example of passing a mutable reference and then dereferencing it.
pub fn run() {
    let mut x = 5i32;
    println!("1 > {:?}", x);
    alter(&mut x);
    println!("2 > {:?}", x);
}
fn alter(x : &mut i32) {
    *x = 3i32;
}
Listing 1-14

Passing the variable as a reference and dereferencing it to get set

Traits

Most of the Rust we have discussed are fairly common features between languages (most languages have concepts of methods or functions and variables). Traits are very different and are how we create abstraction in Rust. Traits are abstraction layers that work on structs to add functionality to yours or to existing structs within the framework. We will see in the chapters to come they are used extensively to create functionality for existing middleware.

Let’s start in Listing 1-15 with an existing struct that has an implementation with it.
pub struct Person {
    pub name: &'static str,
    pub address: &'static str
    //pub extensions: TypeMap,
}
// pub is implied and not needed here
impl Person {
    //fn address
    pub fn say_hello(&self) -> String {
        format!("Hello {}", self.name)
    }
}
Listing 1-15

A Person struct and its optional implementation

Here we have a struct of People, containing a name and address, and even have an implementation to add a say_hello() call to the struct. Now let’s get into traits; the trait is going to define certain abstractions for your struct; the nice thing about traits is they can be applied to more than one struct; you will just have to implement them for each. So in Listing 1-16, we define a trait of students.
pub trait Student {
    // sets the name
    fn new(name: &'static str) -> Self;
    // gets the name
    fn name(&self) -> &'static str;
    // enrolls in the class
    fn enroll(&self, class: &'static str) -> bool;
}
Listing 1-16

A student trait

The trait defines functions needed for it, but not the implementations, although we could define an implementation there if we need to, but that is not the usual case. Finally, let’s attribute the trait Person to the Student in Listing 1-17.
impl Student for Person { ①
    fn new(name: &'static str) -> Person {
        Person { name: name, address: "" }
    } ②
    // gets the name
    fn name(&self) -> &'static str {
        self.name
    } ③
    // enrolls in the class
    fn enroll(&self, class: &'static str) -> bool {
        println!("Enroll the student in {}", class);
        true
    }
}
Listing 1-17

Implement student on the person

  • ① The syntax to say the implementation of trait Student for Person.

  • ② A new to construct the trait. This is optional.

  • ③ Finally, one of the methods used.

At this point, you have your trait, and person has been implemented for the trait; all that’s left in Listing 1-18 is to instantiate and use it.
use super::people::Student;
// have to include all the fields
let mut p: Person = Student::new("joseph");
println!("Person W/Trait:: {}", p.name);
p.enroll("CS 200");
Listing 1-18

Instantiate and use the trait

You will actually instantiate the structure using the trait itself. This is optional, but then it allows the compiler to know that we have Person with a Student trait attached. Thus, any other functions you pass it to would not know those methods were allowed. This leads us to the other case of using traits; what if we have a trait on an existing struct but want to use the trait in our function? In Listing 1-19 we look at that use case.
mod run2 {
    use super::people::Person;
    use super::people::Student; ①
    pub fn run(person: Person) {
        person.enroll("CS 200");
    }
}
Listing 1-19

Using a trait without instantiating it on a struct

  • ① Adding this line in your module allowed the compiler now is required in this case.

The addition of the Student along with the Person allows the compiler to know the Person will have Student functions as well; this is what allows us to add traits to structs that we didn’t create in our application.

Typemap

One quick final topic about traits, if you noticed we could add different traits to one struct, but since they are only allowed to define functions, it’s hard to add any extra data functionality to separate the traits out. You can only work with the data, and you really don’t want to add data fields for one that you don’t use for the other. For example, a student trait may have classes, advisor, and so on. And a faculty trait could have classes taught and so on. One useful way around this limitation is with TypeMap (https://github.com/reem/rust-typemap). TypeMap allows us to store key/values in a tuple that are tied to a struct and then look them up that way accordingly. In Listing 1-20 we have an example of using the TypeMap.
use typemap::{TypeMap, Key};
struct KeyType;
#[derive(Debug, PartialEq)]
struct Value(i32);
impl Key for KeyType { type Value = Value; }
#[test] fn test_pairing() {
    let mut map = TypeMap::new();
    map.insert::<KeyType>(Value(42));
    assert_eq!(*map.get::<KeyType>().unwrap(), Value(42));
}
Listing 1-20

Typemap example

We will use this later with the iron framework to add middleware data to Requests. In addition, there are a few other features using Arc<Mutex<> and the async/await pattern that we will dive into in later chapters when we actually need to use them.

Cargo

Compiling and running code is obviously a must have for any code written, and while we can always compile and run applications from the command line with the language compiler, eventually (usually early on) a package manager (or 8 if you are Java) comes out. Rust is no exception to that of course, and hence we have Cargo. Cargo is the default package manager used by Rust and for Rust projects and in all the coming chapters is what we are going to be using to run our examples and applications. Luckily, Cargo is distributed with by default so we do not have to take any extra steps to get it working.

Cargo will set up a fairly straightforward directory structure for you; you will have a src folder and inside a main.rs with a fn main method. This will be the default entry point into the application. There a few commands we can use to run it:
  • cargo run – Runs the binary or the source application

  • cargo test – Runs all the tests in the application

  • cargo doc – Creates the documentation from the application using the commenting

  • cargo bench – Benchmarks the package

  • cargo build – Builds the application into an executable

The two commands you will use the most are run and test. The build you will normally run when performing a release, so that will occur when you run cargo build --release. There are also third-party extensions to add more functionality to your cargo releases. You can find a various amount here: https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands.

Feature Flags

Feature flags are a way of activating code at compile time or runtime based on a given feature. We will use this when importing crates in the application. With feature flags, you may want to activate certain set of code vs. other sets. For example, inside the ORM we will be using for the database, you could use different versions of the UUID crate. This is quite common in Rust. Feature flags are first defined in your Cargo.toml file and then activated at compile time in your code by encapsulating the method, block, and module with #[cfg(feature = "feature-name")] that would then activate that set of code when the feature-name is supplied. I wanted to mention them because while I won’t actively be using them in the chapters, I will be using them in the source code. Most of our code builds up on itself as we go along either by adding new services or new modules. However, there are many instances that as we progress from chapter to chapter, we have to change a method signature or drastically update. In these instances, I have added feature flags to the code that accompanies this book. The flag full will always be for the final book, but often I use ch05 to compile the code for that specific chapter. The README.md for each module will tell you which flags are supported for that application; of course, you can also look at the Cargo.toml to find out as well.

Creating a Release

One of those extensions is a cargo-release that adds extra functionality to the cargo build release process. You can install the cargo-release plug-in with the command cargo install cargo-release; the only requirement is your project needs to be managed by git:
  1. 1.

    It first will check that the current working directory is git clean; now this usually won’t affect you since releases should be performed on a build system, but it’s useful to know you do not have any outstanding commits before creating a release.

     
  2. 2.

    It bumps the release level if set. This one is a bit more complicated, in that there are different rules based on the level you are releasing, and has to do with the version you set in your Cargo.toml. If you had marked it as pre-release (i.e., 0.1.0-pre), it will simply remove the pre. If the level is a patch and there is no pre-release associated with it, then it will bump the minor version. If the level is minor, it will bump the minor (i.e., 0.1.0-pre to 0.2.0), and if the level is major, it will bump the major version (0.1.0 to 1.0.0). You can also use alpha, beta, and rc for levels as well. We will use this for our application on the hardware being released, but won’t be using it for.

     
  3. 3.

    Run cargo publish if you want to publish this project; this is useful for crates you are creating for public consumption.

     
  4. 4.

    It generates the rustdoc and pushes to gh-pages (optionally); again this is good for a projection that is for a public crate hosted on github.

     
  5. 5.

    It generates a git tag with the number of the version; this is required so that you can easily see the source code associated with a given release.

     
  6. 6.

    In the main, it will then bump the version for the next development cycle.

     
  7. 7.

    Finally, it pushes these changes, the bump and the tag, to git.

     

Summary

In the introduction, we presented the requirements and goals of the book and described the applications we will be creating for it. We also covered some topics related to web and board applications as well as ran through some basic Rust language tutorials. In the next chapter, we will start coding our first microservice.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.114.132