Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
Today weâll experience a sampler of Rust. Iâll provide links that will get you set up, then youâll solve the first Project Euler problem in Rust. Youâll see some of the syntax, and learn what a macro is. I hope to show you that the language is robust, easy to use and blindingly fast! It also boasts fearless concurrency which weâll leave for another post but is a huge selling feature.
Why Rust you may ask? It really depends who you are! University armed me with JavaScript and Python, allowing me to pursue web based projects. Recently, Iâve needed to work closer to the metal to achieve quicker code execution. Rust is quite different to the languages mentioned above as it lacks a garbage collector, and is statically typed. If you run into issues, the compiler error messages are usually great enough to act like an expert pair programmer, otherwise the community is welcoming and helpful.
Check out these companies using Rust in production.
Iâll assume you have some familiarity with executing commands in the terminal or command prompt, and know some programming terminology (like what functions are).
Letâs start with the first Project Euler problem. The problem wants us to find the sum of all the multiples of 3 or 5 below 1000. Before we even tackle the problem weâll set up and install Rust. Weâll create a project and discuss the difference between a macro and a function. Youâll see how to write unit tests as well as generate pretty documentation. Finally weâll solve the problem using a loop, and then functionally with an iterator. Itâs gonna be a blast đ.
Installing and Setting up Rust
Many people have worked very hard to make the installation process great, so Iâll point to those resources. If you have Rust installed and you enjoy your setup, please feel free to skip this section. Otherwise, letâs get Rust!
Rustup is a âtoolchain installer for the systems programming Rustâ. Basically it handles everything for you, and will get you up and running very fast. Go to https://www.rustup.rs/ and follow the instructions for your platform.
Rustup has some nifty features. You can add offline documentation by running the following command rustup component add rust-docs. To access your offline rust documentation type: rustup doc in your terminal or command line. A browser will open with:
- The Rust Bookshelf
- API Documentation
- Extended Error Documentation (for investigating compiler errors)
The Rust Bookshelf is fantastic, and is a great place to go for all levels of Rust users. Online link to these resources here! These resources can be life saving when youâre in the wilderness, writing Rust without wifi đïž.
Are we (I)DE yet? lists the code editor support for Rust. As of writing, VSCode and IntelliJ IDEA have the best support. VSCode is a fantastic free option, and is what IÂ use.
Again there are other posts that cover complete setups. I recommend ShaderCatâs post or Asqueraâs post. The community is working hard improving the developer experience and these posts may become out of date quite quickly. Searching for the Rust Language Server (RLS) may give more up to date instructions for setting up an IDE.
Of course you can just code in notepad⊠The compiler is helpful and I respect your decision.
CargoâCargo is the Rust package manager.â ~ The Cargo Book
Cargo is to Rust as NPM is to JavaScript, or pip is to Python, or RubyGems is to RubyâŠsort of?
Cargo sets up projects, installs dependencies, builds projects, runs tests, generates documentation and uploads your libraries to crates.io. This is a perfect time to start working on our Project Euler problem. To check that everything installed properly run cargo version in your terminal:
Your version number does not need to match.
Build yourself a new project with the command cargo new euler_sum --bin.
Use `cargo help` to learn more
This command tells Cargo to set up a new application called âeuler_sumâ in a new folder. By default cargo creates a library, so weâve used--bin to tell Rust to create an application (âbinâ is short for binary). Use cd euler_sum to change your directory into the application folder. This is what you should find:
.âââ Cargo.tomlâââ src âââ main.rs
1 directory, 2 files
The Cargo.toml file is your projects manifest or metadata. If youâre familiar with JavaScript, itâs similar to the package.json file. You list your dependencies here. More information about it can be found in The Cargo Book here, and you can learn about the cargo options with cargo help.
main.rs contains a tiny Rust program:
fn main() { println!("Hello, world!");}
Run the code with cargo run (while in the folder with the Cargo.toml file).
STOP AND CELEBRATE! Youâve run your first Rust program!!!!đđđ
fn is the way you declare a function. All application projects require a main function as an entry to your program. This function has no arguments, and doesnât return anything.
The body of the function contains this word: println!. This is called a macro. Itâs looks like a function but it ends with an !. Rust uses macros to do very powerful things, and often libraries use them to be very clever. Letâs quickly learn what makes them different from just a function.
Macro detour
Macros allow you to generate code based on patterns! If you need to copy paste code with minor changes, you could write a macro that writes the code for you. When you compile your project, macros are all expanded (written) first, and then the code is compiled as if youâd written what the macros generate. Basically macros write code for you. Letâs have a look at what the println! macro looks like expanded. Run the following command:
$ rustc src/main.rs --pretty=expanded -Z unstable-options
Hereâs my output:
âmacros write code for youâ
Notice that println! has cleverly generated code to print âHello, world!â to the terminal. Because macros are able to pattern match, different code is generated based on different inputs! Therefore you can use println! to format strings as well:
println!("Hello, {}!", "lovely humanoid");// prints -> Hello, lovely humanoid!
println!("Hello, {name}! Want {thing}?", name="Rust", thing="hugs");// -> Hello, Rust! Want hugs?
println!("{num:>0width$}", num=42, width=4);// -> 0042
Another benefit of pattern matching is compiler errors if you muck up the macroâs arguments. If you write println!("{}");, the compiler will helpfully say "error: 1 positional argument in format string, but no arguments were given" as well as a cool diagram. You should definitely try it.
Exercises detour đ€
- What do the println! macroâs above expand to? The answer may surprise you. More or less code than you expected?
- Investigate more string formatting options here(Rust by Example) and here(docs).
- Guess and then find out what the below code outputs:
println!("{0} {1}'s {0} {1}, no matter how small!", "a", "person");
Writing the tests
Because we all want our code test driven as well as type driven, youâre obviously itching to find out how to write tests! All we need here is a simple unit test. If you were writing a library, you could also write doctests.
A test is merely a function that you annotate with an attribute.
#[test]fn simple_test() { assert_eq!(solution(10), 23);}
The #[test] attribute tells Rust that this is a testing function. Therefore this function runs when cargo test is run. assert_eq! panics if the two arguments arenât equal, thus failing the test. You can also use assert! which takes only a single argument, checking for the argument to evaluate to true.
Run the test using cargo test. (It should error because we havenât defined the solution function yet.) If you want to see the tests pass, replace the assert_eq!(solution(10), 23); with assert!(true);.
Generating documentation
Itâs time to start coding the Project Euler problem. Weâll write the solution in a function called solution.
Letâs start with the type of the solution function:
pub fn solution(max: u64) -> u64 { unimplemented!}
This is a public function, that takes an argument max of type u64 and returns a u64. A u64 is a numeric type, called an unsigned integer. These are integers that fit into 64 bits that can only be positive. This immediately tells us that we cannot pass negative numbers into this function, and the function will never return a negative number.
Just for fun lets generate some documentation.
Rust comments can either be the double slash // at the start of the line, or /* multiline comment */. These do not generate documentation. Triple slashes /// before a declaration do generate documentation:
/// `solution` function solves the first Project Euler problempub fn solution(max: u64) -> u64 { // A boring comment unimplemented!()}
If you add this code and run the cargo command cargo doc --open, your browser will open with freshly generated documentation! The documentation supports markdown, meaning you can add headings and links. Read more about documentation generation here!
A rough solution to the problem
The below code should look slightly familiar to you. Try to understand whatâs going on before reading on. We will be making this much simpler, but this is a great starting point.
pub fn solution(max: u64) -> u64 { let mut result = 0; let mut i = 0; loop { if i >= max { break; } if i % 3 == 0 || i % 5 == 0 { result += i; } i += 1; } return result;}
Most of this should be fairly self explanatory except maybe the mut. mut is a way of telling Rust when a variable should be mutable. Here weâre saying that result and i need to be mutable. Without mut we would get an error when trying to execute result += i or i += 1. Delete mut and see the error for yourself. In Rust variables are immutable by default, unless explicitly mutable. Rust functions must also explicitly declare if theyâre going to be mutating arguments passed in. Function behavior is explicit in the type signature.
There are a lot of things we can clean up here. Firstly imperative code is quite verbose. Instead of using the loop and break, we can use a for loop over a range, like so:
pub fn solution(max: u64) -> u64 { let mut result = 0; for i in 0..max { if i % 3 == 0 || i % 5 == 0 { result += i; } } return result;}
Notice we donât have to manage the variable i anymore. 0..max is the range, and the for loop iterates over the range. A for loop can iterate over any type that implements the Iterator trait. We wonât cover traits here, but for now think of it as an interface (although itâs much more). Because the range implements Iterator, you can use iterator methods like map, filter, and fold.
Filter lets you filter the elements based on a predicate, or a function that returns a boolean. We can thus move the if conditionals into a filter to only get the i values we want:
pub fn solution(max: u64) -> u64 { let mut result = 0; for i in (0..max).filter(|n| n % 3 == 0 || n % 5 == 0) { result += i; } return result;}
What is this funny syntax?
|n| n % 3 == 0 || n % 5 ==Â 0
This is a closure. Itâs an anonymous function that can be passed into another function. |n| is the argument/s, and the rest is the function body. The same exact closure can also be written like so (with an explicit function body and return statement):
|n| { return n % 3 == 0 || n % 5 == 0;}
In Rust, if your return statement is on the last line of a function, you can omit the return and semicolon to return implicitly. Using that trick we can remove the return from our function:
pub fn solution(max: u64) -> u64 { let mut result = 0; for i in (0..max).filter(|n| n % 3 == 0 || n % 5 == 0) { result += i; } // Implicit return below: result}
There is still one annoying thing. We have a mutable result variable hanging around. In a larger program this variable could be mutated elsewhere accidentally, and needs to be managed separately. Summing up the values from the iterator doesnât require another variable and can be done using iterator methods! You could use the fold or sum method.
pub fn solution(max: u64) -> u64 { (0..max).filter(|n| n % 3 == 0 || n % 5 == 0).sum()}
Read more about iterators here! There are many iterator methods and they can sometimes be used solely to solve problems. We could also easily introduce fork-join parallelism with a library such as Rayon.
Call your new solution function from the main function and print out your answer. Run the program with cargo run.
đCongratulations for completing the first Project Euler question in Rust đ. I hope you enjoyed this whirlwind tour! In the future I hope to delve deeper into traits (especially the Iterator trait), macros, concurrency and functional programming. Maybe Iâve convinced you to continue to explore this mind expanding language.
If you liked the post, please show it with đ! I love feedback to please leave a comment or message me on Twitter. I also love ideas, so if there is a blog post you wished existed on Rust, Iâd be happy to write it for you!
Follow me on Twitter: @spyr1014
Thank you for reading! â€
References
- Fearless Concurrency-https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html
- Rustup-https://www.rustup.rs/
- VSCode-https://code.visualstudio.com/
- Are we (I)DE yet?-https://areweideyet.com/
- IntelliJ IDEA-https://intellij-rust.github.io/
- Rust documentation link-https://www.rust-lang.org/en-US/documentation.html
- Rustdocâââhttps://doc.rust-lang.org/beta/rustdoc/what-is-rustdoc.html
- The Cargo Book (Cargo.toml)-https://doc.rust-lang.org/cargo/reference/manifest.html
- Iterators: https://doc.rust-lang.org/std/iter/index.html
Beginner Bites: A taste of Rust, a safe, concurrent and practical language! was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.