Introduction to Rust
Copenhagen Tech Polyglot meetup
march 18th, 2015
Who am I?
- Andor Uhlár
- Backend dev at Robocat 😸
- FOSS entusiast
How I use Rust
- Backed services
- Replaced Python
- One-off utilities
- and a lot more!
Keep these in mind
Rust 1.0.0-alpha2
All code is safe, except where otherwise noted
unsafe {}
The Rust Book!
Hello World!
fn main() {
println!("Hello world!")
}
function
name
macro
string
The basics
Variable bindings
- Immutable by default
- Left-hand side is a pattern
- Local type inference
let x = 5;
let y = 3.14;
let is_rust_fun = true;
// Immutable, erroneous
let foo = 1;
foo = 2;
// All good!
let mut bar = 10;
bar = 30;
// Redefinition is also allowed
let foo = 1;
let foo = 2;
// Error
let a = b = 1;
// Valid
let (a, b) = (1, 2);
// Type inference is cool
let c: int = 42;
Primitive types
- Integer types
- signed-unsigned
- size: 8, 16, 32, 64 bits, machine-sized
- Floating-point
- f32, f64
- Booleans
- Static array
- Compile-time sized!
// Integers
let a = 1u8; // unsigned, 8-bit
let b = 2i16; // signed, 16-bit
let c = 23u64; // unsigned, 64-bit
let d = 100isize; // signed, machine-sized
// i8, i16, i32, i64
// u8, u16, u32, u64
// isize, usize
// deprecated
let e = -200int; // signed, same as isize
let f = 0uint; // unsigned, same as usize
// Floats
let pi = 3.1415f32;
let phi = 1.1618f64;
let tau = 6.2831;
// Boolean
let rust_rocks = true; // type bool
let or_not = false;
// Static array
let fib = [1, 1, 2, 3, 5, 8]; // [int; 6]
let zeros = [0; 10]; // [int; 10];
More primitive types
- Tuples
// Tuples
let coords = (10, 20);
let nothing = (); // unit or zero-tuple
let today = (2015, 3, 18);
let (year, month, day) = today; // destructuring
let year = today.0;
let month = today.1;
// Slices are denoted by [T]
// eg. [u8], a byte slice
- Slices
- view into memory
- continuous
- can't manually make one
- "dynamic array"
- Strings a bit later
Blocks
- Expression-based
- everything is an expression (terms and conditions 😋)
- value of block is value of last statement
- Usual scoping rules
- Shadowing important
// Last expression in block
let x = 5;
let y = {
let z = x * 3;
z - 2
};
// () is a perfectly valid value
let a = {
let b = 10;
};
// a will be ()
// Usual scoping
let a = 10;
{
let b = 10;
}
let c = a + b; // Oops!
// Shadowing
let a = 10;
let b = {
let a = 20;
a * 2
};
// b will be 40, not 20
If branching
- Parentheses optional
- Curly braces required
if foo == 42 {
// do some work
}
if bar + 10 == 84 {
// true branch
} else {
// false branch
}
if a == 1 {
// 1 case
} else if a == 2 {
// 2 case
} else if a == 3 {
// 3 case
} else {
// other cases
}
If cont.
- Strictly boolean condition
let x = 1;
// Nope
if x {
}
// Good
if x == 1 {
}
// if is also an expression
let a = if planets_aligned { 1 } else { 2 };
// Types must match! This is an error.
let b = if true { 1 } else { 2.0 };
let c = if x % 2 == 0 {
x * 2
} else {
x + 1
};
let d = if foo {
c * 2; // <- common mistake
} else {
//...
}
- Blocks still evaluate to last expression
Match
- "switch on steroids"
- very powerful
- No fall-through
- No unreachable cases
- overlaps are ok, though
- Guard blocks
- if something...
- Still expression-based
- To be continued!
match x {
1 => ...,
2 => ...,
3 => ...,
_ => ...,
}
match y {
x if x % 2 == 0 => x - 1,
x if x % 2 == 0 && x > 100 => x + 3,
...
}
let y = match x {
a if a < 0 => -a,
b if b > 100 => b - 100,
c => c
};
Functions
- Nothing exotic
- Type inference local only
- Parameter list
- between parens
- name: type
- comma-separated
- Return type
- Single return type
- Tuples are cool
- Implicit ()
// fn keyword, name
fn laugh() {
// body
println!("ha ha!");
}
fn main() {
// invocation
laugh();
laugh();
laugh();
}
fn double(x: int) -> int {
x * 2
// return x * 2;
// both are fine and equivalent
}
fn add(a: int, b: int) -> int {
return a + b;
}
fn foo(x: int) -> (int, int) {
(x + 1, x - 1)
}
fn main() {
let four = double(2);
let three = add(1, 2);
let (twelve, ten) = foo(11);
}
Functions cont.
- Diverging types
- Just think of it as "doesn't return" for now
fn diverging() -> ! {
}
fn main() {
diverging(); // this call will never return
}
Structs
- Classic, C-like structs
- Declaration
- name: type,
- Initialization
- name: value,
- Mutability transient
struct Date {
year: int,
month: int,
day: int
}
struct Meetup {
number_of_attendees: int,
location_coords: (f32, f32),
date: Date
}
fn main() {
let today = Date { year: 2015, month: 3, day: 18 };
let polyglots = Meetup {
number_of_attendees: 35,
location_coords: (12.34, 56.78),
date: today
};
polyglots.number_of_attendees = 34; // not ok!
polyglots.date.day = 20; // not ok!
let mut polyglots = polyglots;
polyglots.date.day = 21; // ok now
}
Tuple- and unit structs
- Named tuples
- 1-tuple
- Newtype
- Wrapper type
- Unit struct
struct Foo(i32, f64);
struct Bar(bool, (i32, i32));
// Different types!
struct Meter(f32);
struct Feet(f32);
struct Placeholder;
fn main() {
let my_height = Meter(1.89);
let mut other = Feet(6.4);
other = my_height; // uh-oh!
let placeholder = Placeholder;
}
Enums
- Pick a name
- sum type
- discriminated union
- disjoint union
- variant type
- and many others...
- Variants can be
- unit-like
- tuple-like
- struct-like
- Can mix and match
enum Icecream {
Vanilla,
Chocolate,
Strawberry
}
enum Shape {
Circle(f32), // radius
Square(int), // side
Rectangle(int, int), // width and height
}
enum Foo {
Bar,
Baz(int, f64, (bool, u32)),
Qux {
a: u8,
b: u16,
c: u32
}
}
// Generics woo!
enum Option<T> {
Some(T),
None
}
Enums cont.
- How to use them...
enum Icecream {
Vanilla,
Chocolate,
Strawberry
}
let my_favorite = Icecream::Vanilla;
if your_favorite == my_favorite {
// we're best buddies!
}
enum Shape {
Circle(f32), // radius
Square(int), // side
Rectangle(int, int), // width and height
}
let foo = Shape::Circle(100.0);
let window = Shape::Rectangle(200, 300);
Patterns!
Match with patterns
- It's really powerful
enum Shape {
Circle(f32), // radius
Square(int), // side
Rectangle(int, int), // width and height
}
fn area(shape: Shape) -> f32 {
match shape {
Circle(0) | Square(0) => 0,
Circle(radius) => 3.1415 * radius * radius,
Square(side) => side * side,
Rectangle(width, height) => width * height
}
}
fn main() {
let a = Shape::Circle(100.0);
let area_of_my_circle = area(a);
let b = Shape::Square(50);
let other_area = area(b);
}
Match with patterns
- Not just enums
// Tuples work just fine
let x = (1, 2, 3, 4);
match x {
(1, a, b, 10) => ...,
(5, 10, 9, 2) => ...,
(a, b, 10, c) if a + b == c => ...,
(_, 2, 3, _) => ...,
_ => ...
}
// Structs too!
struct Foo { a: int, b: int }
let x = Foo { a: 10, b: 20 };
match x {
Foo { a: 30, b: 40 } => ...,
Foo { b: 10, .. } => ...,
Foo { a, b: 50 } => ...,
}
impl blocks
methods
impl block
- List of functions
- occasionally something else
- Types
- static (no self)
- borrowing (&self)
- mutably borrow (&mut self)
- moving (self)
- new is not special, just a common idiom
struct Foo {
a: int,
b: int
}
impl Foo {
fn new() -> Foo {
Foo {
a: 0,
b: 0
}
}
fn a_plus_b(&self) -> int {
self.a + self.b
}
fn set_a(&mut self, new_a: int) {
self.a = new_a;
}
fn set_b(&mut self, new_b: int) {
self.b = new_b;
}
}
fn main() {
let mut foo = Foo::new();
foo.set_a(10);
foo.set_b(20);
let x = foo.a_plus_b();
}
Borrow? Mutable borrow? wtf?!
- Borrow
- method borrows value for the time of the call
- Immutable
- I can look at it
- Mutable
- I can look at it and change it
struct Foo {
a: int,
b: int
}
impl Foo {
//...
fn a_plus_b(&self) -> int {
// I am immutably borrowing it
self.a + self.b
}
fn set_a(&mut self, new_a: int) {
// notice mut, I am mutably borrowing
self.a = new_a;
}
//...
}
Borrowing and moving
- Borrow only for the time of the function call
- Moving takes ownership of the value (it consumes the value)
struct Foo {
a: int
}
fn eat_foo(foo: Foo) {
// I own foo now
}
fn borrow_foo(&foo: Foo) {
// I am immutably borrowing foo
}
fn mut_borrow_foo(&mut foo: Foo) {
// I am mutably borrowing foo
}
fn main() {
let foo = Foo { a: 10 };
borrow_foo(&foo); // borrow foo immutably
mut_borrow_foo(&mut foo); // borrow foo mutably
eat_foo(a); // <- consumed here
let b = foo.a; // error, foo moved (consumed)
// it is no longer valid to refer to foo
}
- Demonstrated with functions, same applies to methods
- Borrowing is implicit with methods though
Arrays, vectors, slices
Remember arrays?
let a = [1, 2, 3, 4];
// [int; 4]
let b = [(1, 2), (3, 4)];
// [(int, int), 2];
Enter slices
let a = [1, 2, 3, 4];
// [int; 4];
// All of these are &[int]
let b = &a[..]; // Slice into the entire array
let c = &a[1..]; // Slice from first element
let d = &a[2..3]; // Slice from second to third element
- Same idea as value borrowing
- Borrowing contents of array
- Don't know the size
- But it is bounds-checked
Vectors
- Slices and static arrays aren't very useful on their own
- Standard library type, not special in any way
let mut numbers = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
let slice = &numbers[0..2];
// Macro for initializing a Vec
let other_numbers = vec![4, 5, 6];
Looping
- 3 kinds of loops
- They're all the same, really
- break and continue as expected
while condition {
// ...
}
loop {
// same as while true
}
for elem in iter {
// ...
}
Iterators
let numbers = vec![1, 2, 3, 10, 20];
let mut sum = 0;
for elem in numbers {
sum += elem;
}
- Iterating a Vector
let numbers = vec![1, 2, 3, 10, 20];
let numbers = &numbers[2..];
let mut sum = 0;
for elem in numbers {
sum += elem;
}
- Or a slice
Strings
they're a little special...
String vs &str vs char
- String is owned string type, equivalent of Vec
- &str is string slice, same idea as regular slice
- char is singular character
- Strings (String, &str, char) are guaranteed valid UTF8
- char is a Unicode code point
- They aren't just a bunch of bytes
- O(n) access
let a = "foo"; // &'static str
let b = "bar".to_string(); // String
let c = 'h';
for byte in b.bytes() {
// iterate through individual bytes
// not very useful most of the time
}
for char in b.chars() {
// iterate through code points
}
for grapheme in b.graphemes() {
// This is what you'll want most of the time
// iterate through graphemes
}
String literals
let a = "foo"; // Simple string literal
let a = "\t\n"; // The usual escape sequences
let a = "\u{56}"; // Unicode escape
// Bytestrings, type is &[u8]
let bytestring = b"abcdef\x12\x00"; // Doesn't have to be valid UTF8
// Raw strings, they don't escape anything
// r, followed by variable number of #s, and a "
// closed by a " plus an equal number of #s
let rawstring r###"hello world """ # foobar ""###;
// this will be:
// hello world """ # foobar "
Traits
Traits
- Similar in concept to Haskell typeclasses
- Java/C# interface (but more powerful)
- Define a shared set of behaviour
struct Circle {
radius: f32
}
struct Rectangle {
width: f32,
height: f32
}
trait HasArea {
fn area(&self) -> f32;
}
impl HasArea for Circle {
fn area(&self) -> i32 {
self.radius * self.radius * 3.1415
}
}
impl HasArea for Rectangle {
fn area(&self) -> i32 {
self.width * self.height
}
}
Traits cont.
- Can be implemented for any type
- Can provide default implementations for functions
- Can get really complex, this is just a brief intro
trait HasArea {
fn area(&self) -> f32;
fn double_area(&self) -> f32 {
area() + area()
}
}
impl HasArea for i32 {
fn area(&self) -> f32 {
self + self
}
}
impl HasArea for () {
fn area(&self) -> f32 {
0.0
}
}
impl<T: HasArea> HasArea for (T, T) {
fn area(&self) -> f32 {
self.0.area() + self.1.area()
}
}
Using traits
- Generics
- Monomorphized
- Statically dispatched
- Trait object
- Dynamically dispatched
trait HasArea {
fn area(&self) -> f32;
}
fn generic_area<T: HasArea>(x: T) {
// I can do this since I've restricted T
// This function accepts Ts that implement HasArea
let area = x.area();
// This will be resolved at compile time
// and statically dispatched
}
fn dynamic_area(x: &HasArea) {
let area = x.area();
// Unknown, this will be dynamically dispatched
}
Generic vs dynamic dispatch
- Generics
- Monomorphized
- Statically dispatched
- Trait object
- Dynamically dispatched
fn generic<T: SomeTrait>(x: T) {}
fn dynamic(x: &SomeTrait) {}
fn main() {
// I have some concrete T
// That implements SomeTrait
generic(foo);
// I have for example a collection
// in the form &[&SomeTrait]
// Concrete type is only known at runtime
for foo in foos {
dynamic(foo);
}
}
Module system
Module example
mod foo {
fn bar() {}
}
fn main() {
foo::bar(); // error, bar is private
}
mod foo {
pub fn bar() {}
}
fn main() {
foo::bar(); // cool, bar is public
}
Same with everything else (structs, enums, traits...)
Reexports, use, super, self
use foo::bar;
mod foo {
pub use self::qux::bar;
mod qux {
use super::baz;
pub fn bar() { baz(); }
}
pub fn baz() {}
}
fn main() {
bar();
}
Different use-s
// Bring things into scope
use foo; // use module
use foo::bar; // use inner module or item (function, struct, trait, etc.)
use foo::{bar, baz}; // pick a list of them
use foo::{self, bar}; // bring bar AND self, meaning foo here
use foo::bar as qux; // bring in under a different name
use foo::*; // glob import, generally not a good idea
Structure
// main.rs
mod foo;
fn main() {
// ...
}
// foo.rs
pub mod bar {
}
// foo/mod.rs
// Will include foo/bar.rs
pub use bar;
Either, but not both
Executable vs Library
- main.rs (+ fn main) => executable
- lib.rs => library
Becomes a crate
.exe/executable, .so, .dylib, .rlib
Cargo
Build system and dep manager
Cargo.toml
- Dependencies
- Build information
- Should be checked into VCS
Cargo.lock
- Auto-generated by Cargo after every successful build
- Dependencies
- State of the world
- Should only be checked into VCS for executables
- Community crate collection
- Cargo supports regular git repos too
- Website - http://rust-lang.org
- Github - https://github.com/rust-lang/rust
- IRC - #rust @ irc.mozilla.com
- Discussion forum - http://users.rust-lang.org/
- Subreddit http://reddit.com/r/rust
- Playground - http://play.rust-lang.org
Thanks for listening!
rust-introduction
By Andor Uhlár
rust-introduction
Andor Uhlár's Introduction to Rust presentation for Copenhagen Tech Polyglots meetup
- 2,437