Neophyte Rustacean

One month with Rust

My background

Do Java as a job (working on a EE server, JBoss)

Learning Haskell over the past 2 years, some small things

Know Scala, C/C++ and a few other languages

 

Science/math background, interesting in getting industry to actually use all the awesome things that have been invented.

My background - Rust

I had been reading blog posts and articles about Rust for 18 months or so.

 

I had never tried to use it until the start of June, after volunteering to talk about it at BFPG!

Today

  • What is Rust?
  • Quick introduction to basics
  • The interesting parts
    • Traits
    • References/Borrowing
  • My first month
  • Where to now?

 

Many examples shamelessly borrowed from the Rust Book

What is Rust?

  • Personal project by Graydon Hoare, sponsored by Mozilla since 2009, announced in 2010, and self-hosting compiler in 2011, and 1.0 released in May 2015
  • Has gone though many "eras" of development, with massive changes in the language (especially types)
  • Designed as a "systems language", it is strict and impure, but maintains memory-safety
  • Not garbage collected, has ownership system
  • Pays some attention to research (IMHO)

 

What is Rust?

  • Aimed at replacing C/C++ (arguable competitor to Go)
  • Zero-cost abstraction
  • Interoperability with C-compatible code
  • Efficient
  • Immutable bindings by default

 

Rust is memory-safe (outside of unsafe blocks):

  • No use-after-free or double-free
  • No data races
    • When multiple pointers access the same memory without synchronisation, and at least one can write

What is Rust? Changes

  • Rust has had a few 'eras' which are fairly different
    • The large changes between Rust eras are the features not the goals.
  • Many experiments to see what would be useful and good.
  • Mozilla's "Servo" HTML renderer is a test-bed for much of Rust.

 

Rust follows Firefox-style release cycle: every 6 weeks master becomes beta, old beta becomes release.

What is Rust? Community

The Rust language and standard library is changed with the use of RFCs. (GitHub issues). Core developers use this too and open for public comment

 

The community is around a third each of systems language people (C/C++ etc), scripting language people and functional language people. Very open to ideas from FP, as long as it fits with the goals.

Rust Basics - book example

use std::io;

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();

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

    println!("You guessed: {}", guess);
}

Rust Basics - features

Rust has many of the expected things:

  • Pattern matching (with guards) / destructuring
  • Tuples
  • if, for/while loops
  • Higher order functions
  • newtype (working on generalised newtype deriving)

Rust types

  • Large variety of "primitive" types (u8, i32, f64, etc.)
  • structs
  • enums
  • Rust types can be parameterised over types and lifetimes (more on lifetimes later)
  • No higher-kinded types. Yet.

 

Be careful reading Rust information online, much of it is about earlier versions.

Rust types

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

fn main() {
    let origin = Point { x: 0, y: 0 }; // origin: Point
    println!("The origin is at ({}, {})", origin.x, origin.y);
}
fn main() {
    let mut point = Point { x: 0, y: 0 };

    point.x = 5;

    println!("The point is at ({}, {})", point.x, point.y);
}

Rust types

struct Point {
    mut x: i32, // not valid
    y: i32,
}
enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

Rust types

enum Option<T> {
    Some(T),
    None,
}
fn takes_two_of_the_same_things<T>(x: T, y: T) {
    // ...
}

Traits

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

trait HasArea {
    fn area(&self) -> f64;
}

impl HasArea for 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=3.0 };
    let a = c.area()
}

Traits

Traits are very heavily used in Rust:

  • Core language parts (e.g. Copy, Deref) - more later
  • Operator overloading (e.g. Add, Range)
  • Collections

 

From my reading they seem reasonably well designed

 - but haven't used them that much

Stolen many ideas from Haskell typeclasses

Traits

Traits can have associates types (for fundeps) and consts

 

 

 

 

Where clauses are useful for simplification (and more)

impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}

impl <A, D> MyTrait<A, D> for YourType where
    A: TraitB + TraitC,
    D: TraitE + TraitF {}
pub trait Mul<RHS = Self> {
    type Output;
    fn mul(self, rhs: RHS) -> Self::Output;
}

// Mul<f32> for f32. Output=f32
// Mul<f32> for Cmplx<f32>, scalar multiply. Output=Cmplx<f32>
// Mul<Cmplx<f32>> for Cmplx<f32>, complex multiply. Output=Cmplx<f32>

References and lifetimes

Rust's usage of lifetimes and references is arguably the hardest part, but also what makes the language what it is.

 

Related to uniqueness typing, either one mutable reference OR zero or more immutable references.

Ownership

  • Bindings own their data, and it is freed when the binding goes out of scope.
  • When a value is used, the ownership moves

 

let v = vec![1, 2, 3];
let v2 = v;
println!("v[0] is: {}", v[0]);
error: use of moved value: `v`
println!("v[0] is: {}", v[0]);

Ownership - Copy trait

For some types, that isn't needed. The Copy trait indicates that it is safe to copy the bits without compromising memory safety.

 

Privative types have it, and structures that do not contain references can safely implement it.

#[derive(Copy)]
struct Point {
    x: f64,
    y: f64,
}

Ownership - functions

Passing a parameter to a function makes it take ownership

You could pass it back explicitly, but that would be tedious

fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
    // do stuff with v1 and v2

    // hand back ownership, and the result of our function
    (v1, v2, 42)
}


let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let (v1, v2, answer) = foo(v1, v2);

Ownership - borrowing

Rather than do that, use a reference parameter to borrow the value from the caller. Does not de-allocate when a reference goes out of scope.

fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
    // do stuff with v1 and v2

    // just return the result like in any sensible language
    42
}


let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let answer = foo(v1, v2);

Ownership - borrowing

Can use &mut to get a mutable reference.

For safety, cannot have a mutable borrow in scope with any immutable borrowings

 

Borrowings last until the end of the scope.

Ownership - borrowing

These rules prevent iterator invalidation, or use-after-free

let mut v = vec![1, 2, 3];

for i in &v {
    println!("{}", i);
    v.push(34);
}
error: cannot borrow `v` as mutable because it is also borrowed as immutable
    v.push(34);
    ^
note: previous borrow of `v` occurs here; the immutable borrow prevents
subsequent moves or mutable borrows of `v` until the borrow ends
for i in &v {
          ^
note: previous borrow ends here
for i in &v {
    println!(“{}”, i);
    v.push(34);
}

Ownership - lifetimes

All references have a lifetime which describes how long they are valid for, and they cannot be used outside that lifetime. Often implicit, they can be made explicit

 

 

 

 

However when in structs, lifetimes must be explicit

fn foo(x: &i32) { // implicit
}

fn bar<'a>(x: &'a i32) { // explicit
}

Miscellanea - concurrency

Memory safety for data race prevention helps a lot for doing concurrency safely.

 

Two important traits:

  • Send - things that can be moved to another thread
  • Sync - things that can safely be used by multiple threads at once

Miscellanea - memory

A very important thing I have not mentioned yet is dynamic memory allocation. Returning a structure to the caller is common.

 

Box is an important type which represents a heap-allocated structure. Only one reference at one

 

Rc is a reference-counted structure

Arc is an atomic reference-counted structure, so Sync

 

There is no GC now, but one could be added like Rc/Arc

What Rust doesn't have

  • Guaranteed tail-call optimisation
    • Makes lifecycle handling even more complex
    • Makes deterministic description more complex
    • ABI incompatibility with C
    • Compiler can (and does) optimise using it
  • Garbage Collection
    • Requires a runtime
    • Mostly subsumed by references and Rc/Arc
    • People working on library for it, like Rc/Arc

My month with Rust

  • Look at the Rust Book and Rust By Example site
  • Tried a few trivial things
  • Tried to rewrite a simple log-analysis tool in Rust
    • Rewritten several times in several languages now. Haskell was the last

My month with Rust

Starting off with the basic tutorials and usual "getting started" material is simple, but by following along you don't have to figure out things by yourself.

 

You then try a small app yourself, and suddenly you are eaten by a grue the borrow checker.

My month with Rust - what?

src/main.rs:53:23: 53:41 error: type mismatch: the type `fn(
collections::string::String, parser_combinators::primitives::State<_>)
 -> core::result::Result<(f64,
 parser_combinators::primitives::Consumed
<parser_combinators::primitives::State<_>>), 
parser_combinators::primitives::Consumed
<parser_combinators::primitives::ParseError<char>>>
 {parseTimestamp}` implements the trait
`core::ops::FnOnce<(collections::string::String, 
parser_combinators::primitives::State<_>)>`, but the trait
`core::ops::FnOnce<(parser_combinators::primitives::State<_>,)>` is required
(expected a tuple with 1 elements, found one with 2 elements) [E0281]
src/main.rs:53     (tse).or(unknown).parse_state(input)

My month with Rust - string

My first big problem was with strings - Rust has two types:

  • String, which is a heap-allocated string
  • &str, which is a slice of characters

 

When you create a string you'll normally end up with a String, but most things take &str for flexability.

 

You only need an & character to turn the String into a &String, and the compiler can turn a &String into a &str (deref coercion), but you need to know that.

My month with Rust - errors

Rust has two types of error, failures (Result) and panics.

In Haskell terms, Either and exceptions.

 

Result is widely used, which is good.

 

The 'try!' macro automates chaining potentially failing operations, but knowing some FP it feels like it should be generalised  (i.e. a Monad), but no Higher-Kinded Types yet

My month with Rust - parser

Several parsing libraries exist:

  • parser_combinators based on Parsec
    • used Parsec in the haskell version, but struggled due to lack of function composition, and resulting code felt hacky
  • nom uses Incomplete/Done/Error state machine
    • Fairly verbose

 

The library ecosystem feels in early 'inflationary' stage.

People figuring out best ideas, dealing with Rust changes

My month with Rust - refs

Map, dictionary, hash, whatever you want to call it, the data structure is more awkward to use in Rust.

 

Who owns the data, the creator? the map? shared?

 

So you want to build one up incrementally, using the existing contents along the way? That sounds like mutable and immutable references at the same time...

 

You need to think clearly about it to write the code

What I've skipped

  • Cargo build tool
  • Crates for package distribution
  • Most of the standard library
  • Many language bits
  • Lots of detail

Should you look at Rust?

I think Rust is very interesting, and regardless of how well it goes, we will learn about how well some of these ideas work in practice.

 

The ecosystem is starting to form, we have a big opportunity to get good ideas (from FP or elsewhere) into the Rust world.

 

Learning any language changes the way you think, why not Rust?

Current disadvantages

  • Due to the many changes over time, a lot of information on the Internet is wrong
  • Still at the early stages of good practices and library designs
  • Some features got pushed back to get 1.0 out
    • But they are still being considered
  • Tooling still needs work
    • 1.1 has a --explain rustc option for more detailed error messages

Rusting on hack night?

Explore rust with game development:

http://jadpole.github.io/arcaders/2015/07/04/arcaders-1-0.html

 

Choose a simple tool and write it

 

Rewriting GNU core utils in Rust

https://github.com/uutils/coreutils

Links

http://www.rust-lang.org/

http://doc.rust-lang.org/stable/book/

http://rustbyexample.com/

http://zsiciarz.github.io/24daysofrust/

Made with Slides.com