Rust Intro

Preview:

Citation preview

Rust Intro

by Artur Gavkaliuk

MozillaThe language grew out of a personal project by Mozilla employee Graydon Hoare. Mozilla began sponsoring the project in 2009 and announced it in 2010. The same year, work shifted from the initial compiler (written in OCaml) to the self-hosting compiler written in Rust. Known as rustc, it successfully compiled itself in 2011. rustc uses LLVM as its back end.

Safe, concurrent, practical languageRust is a general-purpose, multi-paradigm, compiled programming language.

It is designed to be a "safe, concurrent, practical language", supporting pure-functional, imperative-procedural, and object-oriented styles.

Object oriented

Structs

Enums

Method Syntax

Generics

Traits

Structsstruct 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);}

Enumsenum BoardGameTurn { Move { squares: i32 }, Pass,}

let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 };

Genericsstruct Point<T> { x: T, y: T,}

let int_origin = Point { x: 0, y: 0 };let float_origin = Point { x: 0.0, y: 0.0 };

Method syntaxstruct Circle { x: f64, y: f64, radius: f64,}

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

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

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

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

Traitsfn print_area<T: HasArea>(shape: T) { println!("This shape has an area of {}", shape.area());}

Functional

Functions

Function pointers

Type inference

Immutable by default

Option and Result

Pattern matching

Lambda functions

Functionsfn add_one(x: i32) -> i32 { x + 1}

Functionsfn add_one(x: i32) -> i32 { x + 1;}

We would get an error:

error: not all control paths return a valuefn add_one(x: i32) -> i32 { x + 1;}

help: consider removing this semicolon: x + 1; ^

Functionsfn plus_one(i: i32) -> i32 { i + 1}

let f: fn(i32) -> i32 = plus_one;

let six = f(5);

Type inference

Rust has this thing called ‘type inference’. If it can figure out what the type of something is, Rust doesn’t require you to actually type it out.

let x = 5; // x: i32

fn plus_one(i: i32) -> i32 { i + 1}

// without type inferencelet f: fn(i32) -> i32 = plus_one;

// with type inferencelet f = plus_one;

Mutabilitylet x = 5;x = 10;

error: re-assignment of immutable variable `x` x = 10; ^~~~~~~

Mutabilitylet mut x = 5; // mut x: i32x = 10;

Optionpub enum Option<T> { None, Some(T),}

Option

fn divide(numerator: f64, denominator: f64) -> Option<f64> { if denominator == 0.0 { None } else { Some(numerator / denominator) }}

Result

enum Result<T, E> { Ok(T), Err(E)}

fn divide(numerator: f64, denominator: f64) -> Result<f64, &'static str> { if denominator == 0.0 { Err("Can not divide by zero") } else { Ok(numerator / denominator) }}

Result

Pattern matchinglet x = 5;

match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), 4 => println!("four"), 5 => println!("five"), _ => println!("something else"),}

Pattern matching

One of the many advantages of match is it enforces ‘exhaustiveness checking’. For example if we remove the last arm with the underscore _, the compiler will give us an error:

error: non-exhaustive patterns: `_` not covered

Pattern matching

// The return value of the function is an optionlet result = divide(2.0, 3.0);

// Pattern match to retrieve the valuematch result { // The division was valid Some(x) => println!("Result: {}", x), // The division was invalid None => println!("Cannot divide by 0"),}

Pattern matching

// The return value of the function is an Result<f64, &'static str>let result = divide(2.0, 3.0);

// Pattern match to retrieve the valuematch result { // The division was valid Ok(x) => println!("Result: {}", x), // The division was invalid Err(e) => println!(e),}

Pattern matching

Again, the Rust compiler checks exhaustiveness, so it demands that you have a match arm for every variant of the enum. If you leave one off, it will give you a compile-time error unless you use _ or provide all possible arms.

Lambda functionslet plus_one = |x: i32| x + 1;

assert_eq!(2, plus_one(1));

Closureslet num = 5;let plus_num = |x: i32| x + num;

assert_eq!(10, plus_num(5));

Function pointersfn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 { some_closure(1)}

fn add_one(i: i32) -> i32 { i + 1}

let f = add_one;

let answer = call_with_one(&f);

assert_eq!(2, answer);

Memory Safe

The Stack and the Heap

Ownership

Borrowing

Lifetimes

The Stackfn foo() { let y = 5; let z = 100;}

fn main() { let x = 42;

foo();}

The Stackfn foo() { let y = 5; let z = 100;}

fn main() { let x = 42; <-

foo();}

Address Name Value

0 x 42

The Stackfn foo() { let y = 5; let z = 100;}

fn main() { let x = 42;

foo(); <-}

Address Name Value

2 z 1000

1 y 5

0 x 42

The Stackfn foo() { let y = 5; let z = 100;}

fn main() { let x = 42;

foo();} <-

Address Name Value

0 x 42

The Heapfn main() { let x = Box::new(5); let y = 42;}

Address Name Value

(230) - 1 5

... ... ...

1 y 42

0 x → (230) - 1

Ownershiplet 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]); ^

Ownershipfn take(v: Vec<i32>) { // what happens here isn’t important.}

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

take(v);

println!("v[0] is: {}", v[0]);

Same error: ‘use of moved value’.

Borrowingfn 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);

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

// return the answer 42}

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

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

// we can use v1 and v2 here!

&mut referencesfn foo(v: &Vec<i32>) { v.push(5);}

let v = vec![];

foo(&v);

errors with:

error: cannot borrow immutable borrowed content `*v` as mutablev.push(5);^

&mut referencesfn foo(v: &mut Vec<i32>) { v.push(5);}

let mut v = vec![];

foo(&mut v);

The Rules

First, any borrow must last for a scope no greater than that of the owner. Second, you may have one or the other of these two kinds of borrows, but not both at the same time:

one or more references (&T) to a resource,

exactly one mutable reference (&mut T).

Rust prevents data races at compile time

There is a ‘data race’ when two or more pointers access the same memory location at the same time, where at least one of them is writing, and the operations are not synchronized.

Lifetimes

1. I acquire a handle to some kind of resource.

2. I lend you a reference to the resource.

3. I decide I’m done with the resource, and deallocate it, while you still have your reference.

4. You decide to use the resource.

Lifetimes// implicitfn foo(x: &i32) {}

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

Lifetimes

You’ll also need explicit lifetimes when working with structs that contain references.

Lifetimesstruct Foo<'a> { x: &'a i32,}

fn main() { let y = &5; // this is the same as `let _y = 5; let y = &_y;` let f = Foo { x: y };

println!("{}", f.x);}

Lifetimes

We need to ensure that any reference to a Foo cannot outlive the reference to an i32 it contains.

Thinking in scopesfn main() { let y = &5; // -+ y goes into scope // | // stuff // | // |} // -+ y goes out of scope

Thinking in scopesstruct Foo<'a> { x: &'a i32,}

fn main() { let y = &5; // -+ y goes into scope let f = Foo { x: y }; // -+ f goes into scope // stuff // | // |} // -+ f and y go out of scope

Thinking in scopesstruct Foo<'a> { x: &'a i32,}

fn main() { let x; // -+ x goes into scope // | { // | let y = &5; // ---+ y goes into scope let f = Foo { x: y }; // ---+ f goes into scope x = &f.x; // | | error here } // ---+ f and y go out of scope // | println!("{}", x); // |} // -+ x goes out of scope

Not Covered

Macros

Tests

Concurrency

Foreign Function Interface

Cargo

and much much more

Resources

The Rust Programming Language. Also known as “The Book”, The Rust Programming Language is the

most comprehensive resource for all topics related to Rust, and is the primary official document of the

language.

Rust by Example. A collection of self-contained Rust examples on a variety of topics, executable in-

browser.

Frequently asked questions.

The Rustonomicon. An entire book dedicated to explaining how to write unsafe Rust code. It is for

advanced Rust programmers.

rust-learning. A community-maintained collection of resources for learning Rust.

Thank You!

Recommended