One month with Rust
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.
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!
Many examples shamelessly borrowed from the Rust Book
Rust is memory-safe (outside of unsafe blocks):
Rust follows Firefox-style release cycle: every 6 weeks master becomes beta, old beta becomes release.
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.
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 has many of the expected things:
Be careful reading Rust information online, much of it is about earlier versions.
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);
}
struct Point {
mut x: i32, // not valid
y: i32,
}
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
enum Option<T> {
Some(T),
None,
}
fn takes_two_of_the_same_things<T>(x: T, y: T) {
// ...
}
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 are very heavily used in Rust:
From my reading they seem reasonably well designed
- but haven't used them that much
Stolen many ideas from Haskell typeclasses
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>
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.
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]);
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,
}
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);
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);
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.
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);
}
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
}
Memory safety for data race prevention helps a lot for doing concurrency safely.
Two important traits:
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
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.
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 first big problem was with strings - Rust has two types:
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.
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
Several parsing libraries exist:
The library ecosystem feels in early 'inflationary' stage.
People figuring out best ideas, dealing with Rust changes
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
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?
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
http://www.rust-lang.org/
http://doc.rust-lang.org/stable/book/
http://rustbyexample.com/
http://zsiciarz.github.io/24daysofrust/