Rust - newgeekorder/TechWiki GitHub Wiki

Rust

Cargo

Basic hello world cargo file

[package]
name = "hello_world"
version ="0.0.1"
author = ["richard"]

[lib]
name="hello"
test = true
plugin = false

[[bin]]
name = "hello"
path = "src/hello.rs"

Cargo dependencies file

[dependencies]
advapi32-sys = "0.1"
curl = "0.2"
docopt = "0.6"
env_logger = "0.3"
filetime = "0.1"
flate2 = "0.2"
git2 = "0.2"
git2-curl = "0.2"
glob = "0.2"
kernel32-sys = "0.1"
libc = "0.1"
libgit2-sys = "0.2"
log = "0.3"
num_cpus = "0.2"
regex = "0.1"
registry = { path = "src/registry" }
rustc-serialize = "0.3"
semver = "0.1"
tar = { version = "0.2", features = ["nightly"] }
term = "0.2"
threadpool = "0.1"
time = "0.1"
toml = "0.1"
url = "0.2"
winapi = "0.1"

[dev-dependencies]
tempdir = "0.3"
hamcrest = { git = "https://github.com/carllerche/hamcrest-rust.git" }
bufstream = "0.1"
filetime = "0.1"

Creating a new Cargo executable folder

 cargo new hello_world --bin

Rust Variables

  • Rust local variables are, by default,
  • Immutable - meaning that you cannot change their assigned value once give
  • Mutability - must be specifically defined

Basic variable declarations:

let foo = 5

and for mutable variables

let mut foo = 5;
foo = 6;
  • rust compiler infers the type of of the declaration however explicit typing can be also provided
let x: int = 5;      // int 
let y: bool = false; // boolean 
let z: char = 'x';   // char

Other primitives include

  • i8, i16, i32, i64 - integer sizes
  • u8, u16, u32, u64 - unsigned integers
  • isize, usize - size depends on the size of a pointer of the underlying machine, signed or not
  • f32, f64 - floating point integers

Arrays and Slices

let a = [1, 2, 3]; // a: [i32; 3]
let mut m = [1, 2, 3]; // m: [i32; 3]

Short hand to initialize an array size, create an array of size 20 will be initialized to 0:

let a = [0; 20]; // a: [i32; 20]

Slices A ‘slice’ is a reference to (or “view” into) another data structure. They are useful for allowing safe, efficient access to a portion of an array without copying.

let a = [0, 1, 2, 3, 4];
let middle = &a[1..4]; // A slice of a: just the elements 1, 2, and 3
let complete = &a[..]; // A slice containing all of the elements in a

Strings

the str is the most primitive string type.. technicall a 'slice'

Rust also has a string Object

let s = "Hello, world.";

A rust string buffer:

let mut s = String::new();               // string buffer
let mut s = String::with_capacity(10);   // fixed size
let s = String::from_str("hello");       // create string object from string literal
  • rust string object has a number of the usual expected methods including:
    • eq
    • push
    • len
    • contains

String vs. &str

The blogged [best practice] (http://smallcultfollowing.com/rust-int-variations/imem-umem/guide-strings.html) In general, you should prefer String when you need ownership, and &str when you just need to borrow a string. This is very similar to using Vec vs. &[T], and T vs &T in general.

This means starting off with this:

fn foo(s: &str) {

and only moving to this:

fn foo(s: String) {

If you have good reason. It's not polite to hold on to ownership you don't need, and it can make your lifetimes more complex.

Conditions and Loops

  • for
  • while Rust supports a more groovy-ish looping syntax
for x in 0..10 {
    println!("{}", x); // x: i32
}

Also loop on a collection

for var in expression {
    code
}

while

Is also pretty normal with with optional braces

let mut x = 1;
  
  while ( x < 10 ) {
    println! ("x is {}", x);
    x += 1 ;
  }

you have the option to break and continue a loop with the usual words of

  • break
  • continue

if

Conditions are pretty normal except braces are optional

let x = 5;

if x == 5 {
    println!("x is five!");
} else if x == 6 {
    println!("x is six!");
} else {
    println!("x is not five or six :(");
}

or you can use the more traditional

if (x == 5 ) { 

Functions

Rust functions are pretty standard compared with other languages. However complications come in regarding the ownership of passed data. There is a good dog example of functions and ownership

fn foo(v: T) { 

The key aspect is around the Rust memory management where the parameters passed may be

  • copied and owned by the function
fn foo(v: T) { 
  • shared by the calling code and thememory borrowed
fn foo(v: &T) { 
  • borrowed by the function and allowed to mutate it
fn foo(v: &mut T) {

Function return are odd for 2 reasons

  • types are slightly oddly defined with ->
  • you have a choice of a return keyword and line terminated without a colon
  • the return line are not terminated with a semi colon
fn add_one(x: i32) -> i32 {
    x + 1  // this returns the result 
}

you could also:

  return x + 1 // with no semi colon 
  return x + 1: /// WITH a semi colon 

If you do provide a semi-colon the compiler will say ```consider removing this semicolon:`` but throws and error and does not compile.

Functions returning Multiple items

I have seen functions listed where there return multiple items

fn method1(a: &str) -> (String, String) {
    let res = method2(a);
    (res.val0(), res.val1())
}

Structs

Structs are ways to logically group data

struct Point {
    x: i32,
    y: i32,
}

Structures can be initalized with :

let mut point = Point3d { x: 0, y: 0, z: 0 };

Structs (for what ever reason) can also have methods associated (implemented) on them

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
} 

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

fn main() {
    let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
    println!("{}", c.area());
}

Collections - HashMaps, Lists

HashMaps are part of use std::collections::HashMap;

testing indicates:

  • in a struct the are initalized with a key and value type
pub struct config {
	transport : String,
	hostname : String,
	output : String,
	os_config  :  HashMap< String, String>
}
  • in a function we can be more generic
let mut contacts = HashMap::new();
contacts.insert("Daniel", "798-1364");

HasMap get returns a result object :

match myConfig.os_config.get( &"KAFKA_CLIENT_ID".to_string() ){
			Some(config) => { println! (" got back value {} ", config); },
			None => { println! ("unknown value ") }
}

or one can skip the result object and get the value direct

 myMap.get(&"hello".to_string()).unwrap() 

however this does run the risk of throwing an exception for unknown values

thread '<main>' panicked at 'called `Option::unwrap()` on a `None` value'
An unknown error occurred

Searching faster

Searching a HashMap<String, ...> with a String can be expensive: if you don't already have one, it requires allocating a new String. We have a work around in the form of find_equiv, which allows you to pass a string literal (and, more generally, any &str) without allocating a new String:

use std::collections::HashMap;
fn main() {
    let mut mymap = HashMap::new();
    mymap.insert("foo".to_string(), "bar".to_string());
    println!("{}", mymap.find_equiv(&"foo"));
    println!("{}", mymap.find_equiv(&"not there"));
}

Exceptions

let guess: u32 = match guess.trim().parse() { 
      Ok(num) => num, 
      Err(_) => continue, 
};

There is also a try! macro as detailed in the blog

Closures

Rust has a number of methods and syntax extensions to indicate what is going on in closures Rust closures work like anonymous functions ..

let plus_one = |x: i32| x + 1;
assert_eq!(2, plus_one(1));

personally a bit of a pointless use case

Because Rust only likes to have one code section to have ownership, there is a move keyword to force the ownership in to the closure

thread::spawn(move || { p.eat(); 
}) 

Result Matching

A number of methods return some variation of a Result object. The output of which can be matched:

Modules and Paths

The mod keyword is used to manage module scope:

mod a {
    pub fn foo() {}
}
mod b {
    pub fn foo() {
        super::a::foo(); // call a's foo function
    }
}
  • and the keyword super is used to begin resolution relative to the parent module

Module references

x;
x::y::z;

Modules are included with the '''extern''' and '''use''' include keywords By default rust pretends the following modules are include .. as if everything starts with

extern crate std;
use std::prelude::*; 

Core Modules

Macros - common static methods

  • assert! - Ensure that a boolean expression is true at runtime.
  • assert_eq! Asserts that two expressions are equal to each other.
  • cfg! Boolean evaluation of configuration flags.
  • column! A macro which expands to the column number on which it was invoked.
  • concat! Concatenates literals into a static string slice.
  • concat_idents! Concatenate identifiers into one identifier.
  • debug_assert! Ensure that a boolean expression is true at runtime.
  • debug_assert_eq! Asserts that two expressions are equal to each other, testing equality in both directions.
  • env! Inspect an environment variable at compile time.
  • file! A macro which expands to the file name from which it was invoked.
  • format! Use the syntax described in std::fmt to create a value of type String. See std::fmt for more information.
  • format_args! The core macro for formatted string creation & output.
  • include! Parse the current given file as an expression.
  • include_bytes! Includes a file as a byte slice.
  • include_str! Includes a utf8-encoded file as a string.
  • line! A macro which expands to the line number on which it was invoked.
  • module_path! Expands to a string that represents the current module path.
  • option_env! Optionally inspect an environment variable at compile time.
  • panic! The entry point for panic of Rust threads.
  • print! Macro for printing to the standard output.
  • println! Macro for printing to the standard output.
  • scoped_thread_local! Declare a new scoped thread local storage key.
  • select! A macro to select an event from a number of receivers.
  • stringify! A macro which stringifies its argument.
  • thread_local! Declare a new thread local storage key of type std::thread::LocalKey.
  • try! Helper macro for unwrapping Result values while returning early with an error if the value of the expression is Err. Can only be used in functions that return Result because of the early return of Err that it provides.
  • unimplemented! A standardised placeholder for marking unfinished code. It panics with the message "not yet implemented" when executed.
  • unreachable! A utility macro for indicating unreachable code.
  • vec! Creates a Vec containing the arguments.
  • write! Use the format! syntax to write data into a buffer of type &mut Write. See std::fmt for more information.
  • writeln! Equivalent to the write! macro, except that a newline is appended after the message is written.

Threading and Channels

  • spawn The spawn function takes in an owned function (which cannot capture any mutable, shared objects) and runs that function in a new thread:
    fn spawn(f: proc())
  • channels provide a way for spawned tasked to communicate back to their parent:
   let (port, chan) : (Port<int>, Chan<int>) = Chan::new();
   let val = node.head;
   do spawn {
      chan.send(f(val));
   }
   let newval = port.recv();
  • The [thread] module contains Rust's threading abstractions.
  • [sync] contains further primitive shared memory types, including:
    • [atomic] and [mpsc] , which contains the channel types for message passing.

Links and References

⚠️ **GitHub.com Fallback** ⚠️