rust
fearless systems programming
Hey, you! If you have yet to install Rust:
- Go to http://172.16.0.20/oscon/StriegelBen
- Download the package for your platform (Windows users without MSVC installed should select the MinGW version)
- Run the installer (Windows & OS X) or decompress and run `./install.sh` (Linux)
-
Run `rustc --version` to ensure that installation was successful
Ben Striegel
@bstrie
rust
fearless systems programming for the modern developer
"A systems language
Pursuing the trifecta
Safe, concurrent, fast."
- Lindsay Kuper
the rust haiku
who uses rust?
- Systems programmers seeking greater safety and reliability
- Application programmers seeking to speed up key parts of their code
- High-level programmers looking to learn their first low-level language
- Companies seeking a maintainable foundation for their stack
- Startups looking to punch above their weight with a combination of efficiency and productivity
fast
"C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for.
And further: What you do use, you couldn’t hand-code any better.".
- bjarne stroustrup
// C
int sum = 0;
for (int i=0; i<x; i++) {
sum += i;
}
# Python
sum = 0
for i in range(0, x):
sum += i
# `sum()` ist verboten
// Rust
let mut sum = 0;
for i in 0..x {
sum += i;
}
# Ruby
(0...x).inject(0) {|sum, i| sum + i}
# Python
reduce(lambda sum, i: sum + i,
range(0, x), 0)
// Rust
(0..x).fold(0, |sum, i| sum + i)
; Assembly
lea eax, [rdi - 1]
lea ecx, [rdi - 2]
imul rcx, rax
shr rcx
lea eax, [rcx + rdi - 1]
// Rust
(0..x).fold(0, |sum, i| sum + i)
Thanks, LLVM
:)
safe
// C, runtime memory error
int* foo() {
int x = 4;
return &x;
}
// Go, safe thanks to GC
func foo() *int {
x := 4
return &x
}
// Rust, does not compile
fn foo() -> &i32 {
let x = 4;
return &x;
}
// Rust, does not compile
fn foo() -> &i32 {
let x = 4;
return &x;
}
error: missing lifetime specifier [--explain E0106] --> return.rs:1:13 2 |> fn foo() -> &i32 { |> ^^^^ help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
// C++, invalidated iterator
for(auto i: v) {
if (i > 0) {
v.push_back(i * -1);
}
}
// Java, runtime exception
for (Integer i: v) {
if (i > 0) {
v.add(i * -1);
}
}
// Rust, does not compile
for i in v {
if i > 0 {
v.push(i * -1);
}
}
error: use of moved value: `v` [--explain E0382] --> invalidated.rs:5:13 2 |> for i in v { |> - value moved here 3 |> if i > 0 { 4 |> v.push(i * -1); |> ^ value used here after move
con-
current
// Go, with data race
x := 0
for i := 0; i <= 10; i++ {
go func() { x += 1 }()
}
// Go, without data race
mutex := &sync.Mutex{}
x := 0
for i := 0; i <= 10; i++ {
go func() {
mutex.Lock()
x += 1
mutex.Unlock()
}()
}
// Rust, does not compile
let x = 0;
for i in 0..10 {
spawn(|| x += 1);
}
error: closure may outlive the current function, but it borrows `x`, which is owned by the current function [--explain E0373] --> race.rs:4:15 4 |> spawn(|| x += 1); |> ^^ note: `x` is borrowed here --> race.rs:4:18 4 |> spawn(|| x += 1); |> ^
// Rust
let x = Arc::new(Mutex::new(0));
for i in 0..10 {
let y = x.clone();
spawn(move || {
let mut z = y.lock().unwrap();
*z += 1;
});
}
Things to note:
- Cloning an `Arc` allows ownership among multiple threads
- The `move` keyword on a closure captures by-value, similar to C++'s `[=]` capture clause
- Mutexes in Rust don't just guard a section of source code, they actually guard access to pieces of data
basics
hello, world!
fn main() {
println!("Hello!");
}
// C
while (x) y;
for (w; x; y;) z;
for (;;) x;
if (u)
v;
else if (w) {
x;
y;
} else
z;
// Rust
while x { y; }
for x in y { z; }
loop { x; }
if u {
v;
} else if w {
x;
y;
} else {
z:
}
primitives
Signed fixed-width integers:
Unsigned fixed-width integers:
Platform-sized integers:
Floating point:
UTF-32 Unicode scalar values:
Booleans:
i8, i16, i32, i64 u8, u16, u32, u64 isize, usize f32, f64 char bool
collections
Fixed-size array of N elements:
Growable, heap-allocated buffer:
Growable, heap-allocated buffer of UTF-8:
Tuple containing several different types:
[T; N] Vec<T> String (T, U, V)
See the `collections` module in the standard library for hashmaps, sets, B-trees, and more.
pointers
Shared reference:
Mutable reference:
Shared slice (pair of pointer and length):
Mutable slice:
String slice:
Heap-allocated smart pointer:
Reference-counted smart pointer:
&T &mut T &[T] &mut [T] &str Box<T> Rc<T>
literals
let a = 1; // integer
let b = 2.0; // float
let c = "three"; // static string slice
let d = '4'; // char
let e = [5, 6, 7]; // fixed-size array
let f = ("eight", 9.0); // tuple
let g = vec![10, 11, 12]; // vector
cargo
Create new library project:
Create new application project:
Build and run in debug mode:
Build and run in release mode:
Build without running:
Run your test suite:
Update dependencies:
View command-line options:
cargo new foo cargo new foo --bin cargo run cargo run --release cargo build cargo test cargo update cargo help
crates.io
- Central package repository for Rust, similar to CPAN, PyPI, and RubyGems.org
- Has grown to 4,900 packages since launching in late 2015
- Serves as a comprehensive public regression test suite for the Rust compiler
- Once uploaded, packages are immutable and irrevokable, to ensure repeatability
exercise #1
Get the code at github.com/jimblandy/exercises
If you have git:
git clone https://github.com/jimblandy/exercises.git
If you don't have git, download and unzip:
https://github.com/jimblandy/exercises/archive/master.zip
Navigate into the exercises/ex1 directory and run `cargo test`
Observe the test failures
Edit the `is_prime` function in src/lib.rs until all tests pass
OPTIONAL: set up Rust support for your favorite code editor by perusing www.rust-lang.org/ides
the cool stuff
ownership
1. All variables are owned by their enclosing scope
2. When a scope ends, everything it owns is deallocated
3. When one variable is assigned to another variable, the original variable becomes inaccessible, because a piece of memory must have only one owner at a time
4. Passing a variable to a function will move ownership of that data into that function's scope, and make the original variable inaccessible
5. Returning a value from a function or scope will move ownership of that data into the enclosing scope
ownership
1. All variables are owned by their enclosing scope
fn foo() { // this scope owns x and z
let x = Foo;
if true { // this scope owns y
let y = Bar;
}
let z = Qux;
}
ownership
2. When a scope ends, everything it owns is deallocated
fn foo() {
let x = Foo;
if true {
let y = Bar;
} // y is deallocated
let z = Qux;
} // x and z are deallocated
ownership
3. When one variable is assigned to another variable, the original variable becomes inaccessible, because a piece of memory must have only one owner at a time
let x = Foo;
let y = x;
// COMPILATION ERROR BELOW
do_anything_with(x);
ownership
4. Passing a variable to a function will move ownership of that data into that function's scope, and make the original variable inaccessible
let x = Foo;
now_ur_mine(x);
// COMPILATION ERROR BELOW
let z = x;
ownership
5. Returning a value from a function or scope will move ownership of that data into the enclosing scope
fn bar() -> Foo {
let x = Foo;
return x;
}
let y = bar();
// Putting it all together...
fn bar(y: Foo) -> Foo {
return y;
}
fn qux(z: Foo) {
// end of the line
}
let v = Foo; // memory allocated here
let w = v;
let x = bar(w);
qux(x);
ownership
There is one and only one way to alter the behavior of ownership: by making a type copyable
Once a type is copyable, moves will no longer render the original variable inaccessible
This is the complete extent of the user's ability to hijack the ownership system itself, i.e. there are no user-defined copy constructors as there are in C++
This mechanism exists because there's really no benefit to treating small, boring data types like booleans or integers as uniquely-owned pieces of data
traits
// Traits as interfaces
trait DoesSomething {
fn do_something(self);
}
impl DoesSomething for i32 {
fn do_something(self) {
println!("Extending basic types!");
}
}
let x = 6;
x.do_something();
generics
fn not_generic(x: i32) -> i32 {
return x;
}
fn totes_generic<T>(x: T) -> T {
return x;
}
// Traits as bounds on generics
trait DoesSomething {
fn do_something(self);
}
fn kinda_useless_generic<T>(x: T) {
// we basically can't do anything with x,
// because we don't know what it allows!
}
fn kinda_useful_generic<T: DoSomething>(x: T)
{
x.do_something();
}
impl DoesSomething for i32 {
fn do_something(self) { println!("OMG!"); }
}
impl DoesSomething for bool {
fn do_something(self) { println!("ZOMG!!"); }
}
fn kinda_useful_generic<T: DoSomething>(x: T) {
x.do_something();
}
let y = 9;
let z = true;
kinda_useful_generic(y);
kinda_useful_generic(z);
traits in the standard lib
- Traits for operator overloading, such as `Eq` for overloading the `==` operator, and `Add` for `+`
- `Hash`, to denote types that can be keys in hash tables
- `Copy`, the magic trait that makes types copyable, and `Clone`, to provide explicit copy constructors
- `Debug`, for rudimentary automatically-generated string formatting for println-debugging
exercise #2
custom data types
structs
struct Foo {
w: i32,
x: usize
}
struct Bar<T, U> {
y: T,
z: U
}
structs
struct Foo<T> {
x: T,
y: i32
}
fn spam() -> i32 { return 5; }
let bar = Foo { x: 1.0, y: 2 };
let qux = Foo { y: 3, x: spam };
println!("{}", bar.y);
println!("{}", (qux.x)());
enums
use SpellCast::{Fireball, BalefulPolymorph};
enum SpellCast {
Fireball,
BalefulPolymorph
}
let x = Fireball;
match x {
Fireball => println!("BOOM!!"),
BalefulPolymorph => println!("MOO!!")
}
enums
use IntList::{Node, Empty};
enum IntList {
Node(i32, Box<IntList>),
Empty
}
let x = Node(2,
Box::new(Node(4,
Box::new(Empty))));
enums
use Option::{Some, None};
// Redundant with the stdlib
enum Option<T> {
Some(T),
None
}
fn foo<T>(x: Option<T>) {
match x {
Some(y) => println!("Something!"),
None => println!("Nothing :((("),
}
}
enums
#[derive(Debug)]
enum SpellCast {
Fireball,
BalefulPolymorph
}
fn main() {
let x = SpellCast::Fireball;
println!("{:?}", x);
exercise #3
borrowing
fn main(x: Foo) -> Foo {
// man, really wish I
// didn't have to return x...
}
fn main(x: &Foo) {
// sheer ecstasy!
}
borrowing
Literally the hardest thing about learning Rust as a newcomer! The dreaded Borrow Checker!! :O
SECRET PRO TIP: The borrow checker only kicks in if you're using references, or are using something that might eventually hold references. If you don't want to deal with borrow check errors, then you can just clone and box everything until you feel comfortable.
DOUBLE SECRET PRO TIP: Download the nightly compiler and pass the experimental flag for new borrow check error message!
TRIPLE SECRET PRO TIP: Just ask on IRC! They're all seasoned borrow check veterans.
the standard library
advanced concurrency
- Channels: multi-producer single-consumer FIFO queues. doc.rust-lang.org/std/sync/mpsc/index.html
- Arc: Atomically-reference counted smart pointer for safely sharing data amongst threads. doc.rust-lang.org/std/sync/struct.Arc.html
- Crossbeam: Lock-free datastructures without GC via epoch-based memory management. Also allows sharing stack frames among multiple threads. crates.io/crates/crossbeam
- Rayon: Drop-in work-stealing parallel iterators. crates.io/crates/rayon
advanced tooling
- play.rust-lang.org, an online Rust compiler, pastebin, and assembly inspector
- rustdoc, built-in documentation generator via source-level doc comments
- rustup.rs, a Rust toolchain manager for easy cross compilation and compiler version wrangling.
- clippy, a comprehensive set of heuristic-based static analysis and code style suggestions for Rust
bonus exercise
getting involved
- Github: github.com/rust-lang
- Forums: users.rust-lang.org
- IRC: #rust on irc.mozilla.org
- Reddit: reddit.com/r/rust
Thanks for coming!
now, go forth and rust!
Get these slides at slides.com/bstrie/rust-oscon
Rust: Fearless Systems Programming
By bstrie
Rust: Fearless Systems Programming
- 1,431