- Background
- What is Rust?
- Why Rust?
- Examples
- Comparison to other languages
- Comparisons to languages with manual memory management
- Comparisons to garbage collected languages
- Comparisons to most other languages
- Getting Started
- Ownership
- Resources
- A systems programming language
- Statically typed
- Strongly typed
- Implicitly typed (sometimes)
- Performance
- No runtime or garbage collection
- Zero-cost abstractions
- Amazing optimizations—for free!
- Reliability
- Abstractions that minimize bugs
- Ownership
- Data races are prevented by the compiler (!!!)
- Thread safety is enforced by the compiler (!!!)
- Productivity
- Powerful type system
- Useful error messages
- Great package manager (
cargo
)
fn main() {
println!("Hello, world!");
}
Output
Hello, world!
- C-like syntax
fn main() {
println!("The answer is {}", combine(20, 21));
}
fn combine(x: u32, y: u32) -> u32 {
let x2 = x + 1;
x2 + y
}
Output
The answer is 42
- Functions denoted with
fn
- Variable declarations are done with
let
- Types come after name (for both functions and variables)
- Integer types specify their size and signed-ness
u32
= unsigned 32-bit integer
- Last statement is implicitly the return value
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
fn main() {
let point = Point { x: 1.5, y: -2.0 };
println!("Distance for {:?} is {}", point, point.distance());
}
- Rust has
struct
s, not classes - Implementation is separate from definition
- You can define "methods" on
struct
s - Type inference means you almost never have to repeat yourself!
enum Direction {
North,
South,
East,
West,
}
fn main() {
let direction = Direction::East;
let turn = match direction {
Direction::North => "forward",
Direction::South => "backward",
Direction::East => "right",
Direction::West => "left",
};
}
- Enumerated types use
enum
- Pattern matching is done with
match
- A
match
block (and pretty much everything else) can be assigned to a value match
expressions must be exhaustive or have a "default" case
- Memory safety
- Use
malloc
andfree
correctly or have undefined behavior and security issues (segfault
s!) - According to Microsoft, 70% of all security bugs are memory safety issues (link)
- Memory safety is impossible in safe Rust!
- Tooling
rustc
: Compiler with amazing error messagescargo
: Modern build system that makes import dependencies easyrustfmt
: A code formatter that everyone usesclippy
: An Excellent lintercargo doc
: Run documentation as tests
- Powerful abstractions
- No
null
(or segmentation faults)! - Return
Option
s andResult
s instead of error codes and mutating parameters - Powerful trait system
- "Functional programming"-esque tools (map, filter, reduce, etc.)
- Faster
- No garbage collection overhead
- Compiler makes lots of optimizations
- More data on the stack vs. the heap
- Less memory
- No garbage collection overhead
- Zero-cost abstractions (e.g. iterators)
- Garbage collected languages need at least five times as much memory to perform as well as explicit memory management (link)
- Predictable performance
- Data is deallocated deterministically
- No garbage collection "spikes"
- Suitable for systems programming
- Access to raw memory when needed
- No runtime
- Data races are prevented by the compiler—fearless concurrency!
- Explicit control over mutability and ownership
- Install Rust compiler and friends:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Create a new Rust project ("crate")
cargo new <name>
- Edit source file
$EDITOR src/main.rs
- Compile and run
cargo run
The Rust compiler enforces three rules for ownership:
- 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.
fn main() {
let x = 15;
if x > 10 {
// s is not yet declared, so not valid
let s = String::from("Hello"); // s is valid here onward
println!("{}, world!", s)
} // s goes out of scope, no longer valid
// println!("{}", s); // This would be illegal
}
- Data is dropped (deallocated) when its owner goes out of scope
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is moved here
// Try to use s1 after move
println!("{}, world!", s1);
println!("{}, you too!", s2);
}
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `std::string::String`,
| which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value borrowed here after move
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
error: could not compile `e03-ownership-wrong`.
To learn more, run the command again with --verbose.
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is moved here
// Try to use s1 after move
println!("{}, world!", s1);
println!("{}, you too!", s2);
}
- Types with dynamic sizes (vectors, strings, etc.) need to be stored on the heap
- Primitive (and
Copy
) types can be copied without being moved
fn main() {
let s = String::from("hello");
let desc = description(&s);
println!("{}: {}", s, desc);
}
fn description(string: &str) -> String {
if string.len() > 10 {
String::from("long")
} else {
String::from("short")
}
}
- If you don't want to give ownership away, you can let something "borrow" your data
- You can either give away many immutable references or one mutable reference