INTRODUCTION TO
RUST
BY TIM BESS
BRIEF HISTORY
Started at Mozilla in 2006.
Mozilla sponsored the language in hopes it could be
used to improve Firefox.
Went on to be used to build Servo.
Pieces of Servo were put into Firefox in the FF
Quantum release.
Browser overall speed was massively improved.
WHAT IS RUST?
Non-GC'd systems level language.
Focused on safety, performance, concurrency, and
interoperability.
Designed to make systems level so ware more
accessible to developers.
WHEN IS RUST USEFUL?
When C-level performance is needed, but without
the associated low level stumbling blocks.
Applications with high cost of failure or where
correctness and refactorability are valuable.
High level languages offer low mental overhead and
speed of development, but aren't usually
performant. Developers can drop down into Rust
where traditionally C has been used.
RUST FEATURES
Will not segfault.
Will not allow many classes of data races.
Will not allow memory leaks.
Zero-cost abstractions.
RUST FEATURES
C
RUST
char* foo = calloc(4, sizeof(char))
strcpy(ptr, "foo");
char* b = foo;
free(foo);
puts(b); // References bad memory
free(b); // Security vulnerability
let foo = String::from("foo");
let b = &foo;
// Pretend foo is moved somewhere or deleted
// Fails to compile!
println!("{}", b)
SAFE REFERENCES
Pointer aliasing and mutability are not mixed
(compile-time guarantee).
Pointers never live longer than the data they hold.
Array types provide safe bounds checking, unlike C.
Nulls cannot be created or accidentally
dereferenced.
SAFE REFERENCES
// Option is defined in stdlib as
pub enum Option<T> {
None,
Some(T),
}
fn get_value() -> Option<String> {
Some(String::from("Foobar"))
}
fn main() {
// Value may either be None, or a Some(string)
let foo = get_value();
// Extract the value from the enum
match foo {
Some(val) => { println!("{}", val); },
None => { println!("No value!"); },
}
}
DATA RACES
Rust guarantees pointers must be exclusively
aliased or mutated.
Send/Sync traits define whether types can be sent
between threads and shared between threads
respectively.
Most structs that own their fields automatically
implement Send.
This prevents many types of data races.
DATA RACES
SEND/SYNC
// Stdlib definition of Send and Sync
pub unsafe auto trait Send { }
pub unsafe auto trait Sync { }
OWNERSHIP SYSTEM
OWNERSHIP RULES
Each value in Rust has a variable that’s called its
owner.
There can only be one owner at a time.
When the owner goes out of scope, the value will be
dropped.
BORROWING RULES
At any given time, you can have either one mutable
reference or any number of immutable references.
References must always be valid.
OWNERSHIP SYSTEM
EXAMPLE 1
fn main() {
{
// foo is owned in this scope
let foo = String::from("bar");
} // foo is dropped and freed
// Fails to compile
println!("{}", foo);
}
OWNERSHIP SYSTEM
EXAMPLE 2
// foo is an owned string
fn takes_string(foo: String) {
// Does things with foo
} // Drops foo
fn main() {
let foo = String::from("bar");
// Ownership is MOVED to takes_string
takes_string(foo);
// Fails to compile
// string was moved into takes_string
println!("{}", foo);
}
OWNERSHIP SYSTEM
EXAMPLE 3
// foo is an immutably borrowed string
fn takes_string(foo: &String) {
// Does things with foo
} // drops borrow
fn main() {
let foo = String::from("bar");
// give an immutable borrow to takes_string
takes_string(&foo);
// Compiles successfully!
println!("{}", foo);
} // foo is dropped
LIVE CODING!
Feel free to code along with me using the
or by .
Rust
playground installing rustup
ZERO-COST ABSTRACTIONS
Rust compiler offers strict guarantees about
lifetimes, mutability, etc. that allow it to do big
compile time optimizations.
Many high level abstractions, like futures and
iterators, are completely torn out at compile time.
ZERO-COST ABSTRACTIONS
EXAMPLE 1
trait Animal {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
impl Animal for Dog {
fn speak(&self) -> String { "Woof".to_string() }
}
impl Animal for Cat {
fn speak(&self) -> String { "Meow".to_string() }
}
fn main() {
let animals: Vec<&dyn Animal> = vec![&Dog, &Cat];
println!("{:?}", animals.iter().map(|x| x.speak())
.collect::<Vec<String>>());
}
ZERO-COST ABSTRACTIONS
EXAMPLE 2
enum Animal { Dog, Cat }
impl Animal {
fn speak(&self) -> String {
match self {
Animal::Dog => "Woof".to_string(),
Animal::Cat => "Meow".to_string()
}
}
}
fn main() {
use Animal::*;
let animals: Vec<Animal> = vec![Dog, Cat];
println!("{:?}", animals.iter().map(|x| x.speak())
.collect::<Vec<String>>());
}
ZERO-COST ABSTRACTIONS
EXAMPLE 3
use std::fmt::Debug;
fn debug_string<T: Debug>(obj: T) -> String {
format!("{:?}", obj)
}
fn debug_string_dynamic(obj: &dyn Debug) -> String {
format!("{:?}", obj)
}
fn main() {
println!("{}", debug_string(vec![1, 2, 3]));
println!("{}", debug_string("foobar"));
println!("{}", debug_string_dynamic(&vec![1, 2, 3]));
println!("{}", debug_string_dynamic(&"foobar"));
}
UNSAFE CODE
Allows for raw pointers, aliasing, mutation, etc. All
the things the Rust compiler normally prevents.
Unsafe should not be used to bypass safety
guarantees, but instead to provide safe abstractions
over unsafe code.
Many stdlib types that offer safe abstractions are
built on unsafe code that has been hand verified.
Necessary for FFI code.
SAFE UNSAFE CODE
MUTEX EXAMPLE
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let mutex = Arc::new(Mutex::new(0));
let c_mutex = mutex.clone();
thread::spawn(move || {
*c_mutex.lock().unwrap() = 10;
}).join().expect("thread::spawn failed");
assert_eq!(*mutex.lock().unwrap(), 10);
}
C INTEROP
Rust has two way FFI support with C.
Structs can optionally use C layout so that C code
can read/mutate Rust structs.
Bindgen can be used to generate Rust definitely for
C foreign functions.
Compatible with C ABI and can generate C header
files for Rust libs through CBindgen.
Because CBindgen generates C header files, many
interpreted languages can call out to Rust just like C.
USECASE: PYTHON
PROS
Approachable for beginners.
Excellent for fast prototyping while requirements
are poorly defined.
Allows for clean, terse code.
Plethora of libraries at your fingertips.
USECASE: PYTHON
CONS
GIL limits parallel processing.
Very slow. Pypy is making things better, but still
needs work to be compatible with legacy Python
libraries.
Difficult to refactor. Efforts like Mypy might improve
this in the future, but would require mass adoption
to be useful in many cases.
OXIDIZING PYTHON
CFFI METHOD
CBindgen + Milksnake allow for relatively seamless
build process for calling out to Rust from Python.
Uses CFFI which allows for compatibility with Pypy.
Limited in that Python must call out to Rust.
No coupling to CPython interpreter.
OXIDIZING PYTHON
CPYTHON INTEGRATION
Link against Python C headers using PyO3.
Directly interacts with CPython interpreter and
allows two way function calls.
Potentially (slightly) lower overhead for calling out
to Rust.
Limited to CPython interpreter.

Intro to Rust 2019

  • 1.
  • 2.
    BRIEF HISTORY Started atMozilla in 2006. Mozilla sponsored the language in hopes it could be used to improve Firefox. Went on to be used to build Servo. Pieces of Servo were put into Firefox in the FF Quantum release. Browser overall speed was massively improved.
  • 3.
    WHAT IS RUST? Non-GC'dsystems level language. Focused on safety, performance, concurrency, and interoperability. Designed to make systems level so ware more accessible to developers.
  • 4.
    WHEN IS RUSTUSEFUL? When C-level performance is needed, but without the associated low level stumbling blocks. Applications with high cost of failure or where correctness and refactorability are valuable. High level languages offer low mental overhead and speed of development, but aren't usually performant. Developers can drop down into Rust where traditionally C has been used.
  • 5.
    RUST FEATURES Will notsegfault. Will not allow many classes of data races. Will not allow memory leaks. Zero-cost abstractions.
  • 6.
    RUST FEATURES C RUST char* foo= calloc(4, sizeof(char)) strcpy(ptr, "foo"); char* b = foo; free(foo); puts(b); // References bad memory free(b); // Security vulnerability let foo = String::from("foo"); let b = &foo; // Pretend foo is moved somewhere or deleted // Fails to compile! println!("{}", b)
  • 7.
    SAFE REFERENCES Pointer aliasingand mutability are not mixed (compile-time guarantee). Pointers never live longer than the data they hold. Array types provide safe bounds checking, unlike C. Nulls cannot be created or accidentally dereferenced.
  • 8.
    SAFE REFERENCES // Optionis defined in stdlib as pub enum Option<T> { None, Some(T), } fn get_value() -> Option<String> { Some(String::from("Foobar")) } fn main() { // Value may either be None, or a Some(string) let foo = get_value(); // Extract the value from the enum match foo { Some(val) => { println!("{}", val); }, None => { println!("No value!"); }, } }
  • 9.
    DATA RACES Rust guaranteespointers must be exclusively aliased or mutated. Send/Sync traits define whether types can be sent between threads and shared between threads respectively. Most structs that own their fields automatically implement Send. This prevents many types of data races.
  • 10.
    DATA RACES SEND/SYNC // Stdlibdefinition of Send and Sync pub unsafe auto trait Send { } pub unsafe auto trait Sync { }
  • 11.
    OWNERSHIP SYSTEM OWNERSHIP RULES Eachvalue in Rust has a variable that’s called its owner. There can only be one owner at a time. When the owner goes out of scope, the value will be dropped. BORROWING RULES At any given time, you can have either one mutable reference or any number of immutable references. References must always be valid.
  • 12.
    OWNERSHIP SYSTEM EXAMPLE 1 fnmain() { { // foo is owned in this scope let foo = String::from("bar"); } // foo is dropped and freed // Fails to compile println!("{}", foo); }
  • 13.
    OWNERSHIP SYSTEM EXAMPLE 2 //foo is an owned string fn takes_string(foo: String) { // Does things with foo } // Drops foo fn main() { let foo = String::from("bar"); // Ownership is MOVED to takes_string takes_string(foo); // Fails to compile // string was moved into takes_string println!("{}", foo); }
  • 14.
    OWNERSHIP SYSTEM EXAMPLE 3 //foo is an immutably borrowed string fn takes_string(foo: &String) { // Does things with foo } // drops borrow fn main() { let foo = String::from("bar"); // give an immutable borrow to takes_string takes_string(&foo); // Compiles successfully! println!("{}", foo); } // foo is dropped
  • 15.
    LIVE CODING! Feel freeto code along with me using the or by . Rust playground installing rustup
  • 16.
    ZERO-COST ABSTRACTIONS Rust compileroffers strict guarantees about lifetimes, mutability, etc. that allow it to do big compile time optimizations. Many high level abstractions, like futures and iterators, are completely torn out at compile time.
  • 17.
    ZERO-COST ABSTRACTIONS EXAMPLE 1 traitAnimal { fn speak(&self) -> String; } struct Dog; struct Cat; impl Animal for Dog { fn speak(&self) -> String { "Woof".to_string() } } impl Animal for Cat { fn speak(&self) -> String { "Meow".to_string() } } fn main() { let animals: Vec<&dyn Animal> = vec![&Dog, &Cat]; println!("{:?}", animals.iter().map(|x| x.speak()) .collect::<Vec<String>>()); }
  • 18.
    ZERO-COST ABSTRACTIONS EXAMPLE 2 enumAnimal { Dog, Cat } impl Animal { fn speak(&self) -> String { match self { Animal::Dog => "Woof".to_string(), Animal::Cat => "Meow".to_string() } } } fn main() { use Animal::*; let animals: Vec<Animal> = vec![Dog, Cat]; println!("{:?}", animals.iter().map(|x| x.speak()) .collect::<Vec<String>>()); }
  • 19.
    ZERO-COST ABSTRACTIONS EXAMPLE 3 usestd::fmt::Debug; fn debug_string<T: Debug>(obj: T) -> String { format!("{:?}", obj) } fn debug_string_dynamic(obj: &dyn Debug) -> String { format!("{:?}", obj) } fn main() { println!("{}", debug_string(vec![1, 2, 3])); println!("{}", debug_string("foobar")); println!("{}", debug_string_dynamic(&vec![1, 2, 3])); println!("{}", debug_string_dynamic(&"foobar")); }
  • 20.
    UNSAFE CODE Allows forraw pointers, aliasing, mutation, etc. All the things the Rust compiler normally prevents. Unsafe should not be used to bypass safety guarantees, but instead to provide safe abstractions over unsafe code. Many stdlib types that offer safe abstractions are built on unsafe code that has been hand verified. Necessary for FFI code.
  • 21.
    SAFE UNSAFE CODE MUTEXEXAMPLE use std::sync::{Arc, Mutex}; use std::thread; fn main() { let mutex = Arc::new(Mutex::new(0)); let c_mutex = mutex.clone(); thread::spawn(move || { *c_mutex.lock().unwrap() = 10; }).join().expect("thread::spawn failed"); assert_eq!(*mutex.lock().unwrap(), 10); }
  • 22.
    C INTEROP Rust hastwo way FFI support with C. Structs can optionally use C layout so that C code can read/mutate Rust structs. Bindgen can be used to generate Rust definitely for C foreign functions. Compatible with C ABI and can generate C header files for Rust libs through CBindgen. Because CBindgen generates C header files, many interpreted languages can call out to Rust just like C.
  • 23.
    USECASE: PYTHON PROS Approachable forbeginners. Excellent for fast prototyping while requirements are poorly defined. Allows for clean, terse code. Plethora of libraries at your fingertips.
  • 24.
    USECASE: PYTHON CONS GIL limitsparallel processing. Very slow. Pypy is making things better, but still needs work to be compatible with legacy Python libraries. Difficult to refactor. Efforts like Mypy might improve this in the future, but would require mass adoption to be useful in many cases.
  • 25.
    OXIDIZING PYTHON CFFI METHOD CBindgen+ Milksnake allow for relatively seamless build process for calling out to Rust from Python. Uses CFFI which allows for compatibility with Pypy. Limited in that Python must call out to Rust. No coupling to CPython interpreter.
  • 26.
    OXIDIZING PYTHON CPYTHON INTEGRATION Linkagainst Python C headers using PyO3. Directly interacts with CPython interpreter and allows two way function calls. Potentially (slightly) lower overhead for calling out to Rust. Limited to CPython interpreter.