Getting started in Rust


#1

I’m starting to learn Rust! This would make an excellent place to learn it! This is my first “program”. All it does is take user input, matches all the integers via regex. Converts the strings to integers, multiply them by 2, and append them on a vector. Then print out the results.

Here’s the dependency

[dependencies]
regex = "0.1.41"

And the script

extern crate regex;

use std::io;
use regex::Regex;

fn main() {
  println!("It's Rust!");

  let mut guess = String::new();

  io::stdin().read_line(&mut guess)
    .ok()
    .expect("Failed to read line");

  let re = Regex::new(r"(\d+)").unwrap();

  let mut results: Vec<i32> = vec![];

  for cap in re.captures_iter(&guess) {
    let mut val: i32 = cap.at(0).unwrap().parse::<i32>().unwrap();
    val *= 2;
    results.push(val)
  };
  
  println!("{:?}", results);
}

My thoughts/discoveries during the process

I used Regex because converting a String to an Array looked like it would be very difficult to figure out. I used a Vector for the new Array because it’s not a fixed size and I can used an undetermined amount of input for it. One thing I found is that when you have a method return a result you get an object wrapper of a result (I believe this to be true, I could be wrong), and each item you return you need to unwrap() to get the raw type result. Parameters preceded with an ampersand are “borrowed” references (as in - they don’t take ownership of the object/memory).

Example output (and input)

It's Rust!
3,4,5
[6, 8, 10]


Getting started

Install Rust. Use cargo new project_name --bin to generate a program directory. Add any dependencies in the .toml file. And edit the src/main.rs with your code. When you’re ready run cargo build, and if you have no errors then run cargo run. If you do see errors you will have a very detailed explanation of what’s wrong.

To get a good idea of how Rust works watch this presentation:


#3

If you use Vim I suggest you add these two Vim plugins to your .vimrc file.

http://github.com/rust-lang/rust.vim - syntax highlighting
http://github.com/racer-rust/vim-racer - auto-completion


#4

What made you want to learn Rust, Dan?


#5

This blog post about writing Ruby methods in Rust Rust to the rescue (of Ruby) by Fabiano B.. I’ve read one other post like it and I’ve seen Yehuda Katz do it in a conference talk.

GoGaRuCo 2014- Let’s Talk About Rust by Yehuda Katz

Also notably Yehuda Katz; who’s one of the authors of Ruby’s Bundler; wrote the package management tool for Rust called Cargo.


For a real getting started in Rust video watch this presentation.

NYLUG Presents: Steve Klabnik on Why Rust? (July 15, 2015)


#6

For Rust TDD it’s as easy as follows.

cargo new example
cd example

No modification to the .toml file. And the simple src/lib.rs

pub fn do_the_thing() -> bool {
  true 
}

And a simple tests/lib.rs

extern crate example; 

#[test]
fn it_works(){
  assert!(example::do_the_thing())
}

And this works! You can further test by proving a failure with assert!(false)

Tip! Any line that’s not the last line within a lexical scope needs a semicolon at the end of the chain of command(s). The last line does not get one. (in the examples above everything is the last line within their lexical scopes)


#7

A great site to learn Rust from is rustbyexample.com

I’ve written a complete and functional combinatorial count rust example here: https://github.com/danielpclark/combinatorics

I will write my experience in the process and what I’ve learned from it here later. I’m too tired to continue today. Zzzz…


#8

Okay, here’s a few things I learned in the process.

  1. Closures (think of lambdas/procs) are written as follows:
my_closure = |a: i32, b: i32| -> i32 { a + b }

Here a and b are paremeters that are set to type 32bit integer. The arrow -> indicates the type to be output which is also a 32bit integer. And in between the mustaches is the block of code to execute and return { a + b }.

Rust doesn’t always require the types to be explicitly set as it can infer what they’ll be. But it makes it easier for you to think and debug it when you know what it should be by specifying it yourself.

I wasn’t able to figure out how to inline test a closure as it complains about not being able to pull it in. So in my code I rewrote my closures as normal methods I could test.

  1. Ranges must be specified from least to greatest (and not the other way around). You can use the collect method to return the range as an Array. To tell collect what specific type of collection to return you do it after a double colon and before parenthesis.
(1..10).collect::<Vec<i32>>()

Using Vecors for Arrays allow you to add and remove from it as it is not a fixed size. When you take a slice of the array from the Vector you get a fixed size array. For example slicing from the Array above will return an Array of type &[i32] … Vectors and fixed arrays have different methods on them and you can’t pass in a fixed Array where a Vector is expected. On the &[i32] type you should call .to_vec() to return a vector.

In case you’re wondering about the part inside Vec[] and &[] is the type of unit the container keeps inside.

Example (double slashes are for comments)

let x = (1..10).collect::<Vec<i32>>();
let slice = x[2..6].to_vec()

Notice I didn’t specify the type on the let side… that’s optional. IF you wanted to specify the type yourself it would look like.

let x: Vec<i32> = (1..10).collect::<Vec<i32>>();
let slice: Vec<i32> = x[2..6].to_vec()

Note: the range is non inclusive so if you want to use a range with variables inclusively add a plus one (1…(i+1))

  1. Case statements are done with match. In Rust the match keyword is best used for case statements and if-else scenarios.
match testing_this {
  1 => println!("It's 1"),
  2 => println!("It's 2"),
  _ => println!("It's anything other than 1 or 2 and the value is: {}", testing_this),
}

Yes the last comma was intentional. It’s in documentation everywhere on demonstrations. I don’t know why they have the comma there. For me it seems to work without it.

The compiler will ensure you have a result for every match situation. The underscore thing _ is kind of like a “what-ever” object you use to say you don’t care about the value.

  1. Null / Nil. There is no such thing as nil in Rust. If you want to implement a null option you have to create an Option object and expect either a Some result or a None result.

  2. When creating a library with cargo new my_thing the resulting code is auto scoped inside the name of the project. In the Cargo.toml file where it says name=“my_thing” you’ve already specified something like a module. In your test file you include it with:

extern crate my_thing;

To test the methods in your integration test file you would write:

#[test]
fn do_the_test {
  assert!(my_thing::some_method) // should return true
}

If you want the methods from the “crate” my_thing available in local scope then you would add the following line near the top of your integration test file:

use my_thing::*;

And that’s that. Everything seems pretty straight forward from there and anything you do wrong it will be sure to tell you about in it’s compiler alert/messages.


#9

Also the official doc by steveklabnik should be metioned: https://doc.rust-lang.org/stable/book :smile:


#10

I’ve released my first Rust “crate”; the equivalent to a Ruby gem. It’s called Array Tool v0.1.0. Currently you give it two vectors and it will return two vectors with all common elements removed. I plan on adding many Array utilities over time that are typically available in other higher languages such as Ruby or Python.


#11

A more advanced instruction for implementing Ruby methods in Rust: A Rusting Rubyist IV

And a cheat sheet for Rubyists to use Rust: Ruby to Rust Cheat Sheet

And a general Rust cheat sheet: Learn X in Y minutes (Where X=rust)


#12

I’ve integrated Rust methods into Ruby in my combinatorics repo and benchmarked it. The Rust methods can easily run about 1000x faster than the Ruby methods.

Here’s the iterations per second results:

# Permutations
#   input    language
(12500, 160) in Rust  35.517k (± 0.4%) i/s -    180.359k
(12500, 160) in Ruby  954.776 (± 2.4%) i/s -      4.836k
(1723, 160) in Rust  202.591k (± 1.6%) i/s -      1.021M
(1723, 160) in Ruby    4.610k (± 3.2%) i/s -     23.300k
(500, 160) in Rust   463.864k (± 5.1%) i/s -      2.319M
(500, 160) in Ruby     8.132k (± 6.0%) i/s -     40.800k
(230, 160) in Rust   685.956k (± 1.9%) i/s -      3.455M
(230, 160) in Ruby    10.592k (± 1.7%) i/s -     53.448k

# Combinatorial Count
#   input    language
(12500, 160) in Rust 939.118k (± 1.8%) i/s -      4.707M
(12500, 160) in Ruby  887.679 (± 1.6%) i/s -      4.488k
(1723, 160) in Rust  948.347k (± 0.8%) i/s -      4.746M
(1723, 160) in Ruby    3.293k (± 2.7%) i/s -     16.650k
(500, 160) in Rust   902.660k (± 5.7%) i/s -      4.526M
(500, 160) in Ruby     4.844k (± 1.7%) i/s -     24.450k
(230, 160) in Rust   912.189k (± 1.7%) i/s -      4.570M        
(230, 160) in Ruby     5.431k (± 1.3%) i/s -     27.350k

#13

My favorite reading source for learning Rust… is its source code. They document like their lives depend on it. My favorite two files to look at are Vectors and Iterators. I can’t help but learn vast amounts. I also peruse the users forum and look at topics I’m not interested in to see peoples thoughts on the language and issues.

Lastly if you choose to maintain a library, this blog post has an amazing tooling setup: Good Practices for Writing Rust Libraries


#14

Might as well tack this one here; I wrote a blog post a while back about Coming to Rust from Ruby