Rust is a multi-paradigm system programming language focused on safety, especially safe concurrency.
Rust is syntactically similar to C++, but is designed to provide better memory safety while maintaining high performance.
Most loved programming language in the Stack Overflow Developer Survey since 2016
One of the fastest in Techempower benchmarks
Tooling, no GC, high level with zero cost abstractions, safety, error handling, pattern matching, compile error messages
Compile times, initial complexity
Extensible via cargo-xxxx
subcommands (like git
), notable:
cargo init "project name"
...
cargo run
cargo init --lib "library name"
fn main() {
println!("Hello, world!");
}
fn main() {
let x = 7; // type inference
let y: i8 = 100;
let z = 6u32;
println!("x, y, z: {}, {}, {}", x, y, z);
}
x, y, z: 7, 100, 6
fn main() {
let _x = 6;
_x = 4;
}
error[E0384]: cannot assign twice to immutable variable `_x`
--> src/main.rs:3:5
|
2 | let _x = 6;
| --
| |
| first assignment to `_x`
| help: make this binding mutable: `mut _x`
3 | _x = 4;
| ^^^^^^ cannot assign twice to immutable variable
Note the helpful message
fn main() {
let mut x = 4; // type inference
x = if x < 3 { 0 } else { x + 1 };
println!("x is {}", x);
}
x is 5
Basis for compile time memory management and safety
fn main() {
let name = String::from("Meir");
greet(name);
}
fn greet(name: String) {
println!("Hello {}", name); // freed
}
Hello Meir
fn main() {
let name = String::from("Meir");
greet(name);
println!("{} was greeted!", name);
}
fn greet(name: String) {
println!("Hello {}", name);
}
error[E0382]: borrow of moved value: `name`
--> src/main.rs:4:33
|
2 | let name = String::from("Meir");
| ---- move occurs because `name` has type `std::string::String`, which does not implement the `Copy` trait
3 | greet(name);
| ---- value moved here
4 | println!("{} was greeted!", name);
| ^^^^ value borrowed here after move
fn main() {
let name = String::from("Meir");
greet(&name);
println!("{} was greeted!", name);
}
fn greet(name: &String) {
println!("Hello {}", name);
}
Hello Meir
Meir was greeted!
Borrows can't mutate values by default
fn main() {
let mut name = String::from("Meir");
add_greeting(&name);
println!("{}!", name);
}
fn add_greeting(name: &String) {
name.insert_str(0, "Welcome ");
}
error[E0596]: cannot borrow `*name` as mutable, as it is behind a `&` reference
--> src/main.rs:7:5
|
7 | fn add_greeting(name: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut std::string::String`
8 | name.insert_str(0, "Welcome ");
| ^^^^ `name` is a `&` reference, so the data it refers to cannot be borrowed as mutable
fn main() {
let mut name = String::from("Meir");
add_greeting(&mut name);
println!("{}!", name);
}
fn add_greeting(name: &mut String) {
name.insert_str(0, "Welcome ");
}
Welcome Meir!
We can tell from the function signature and call that name
is gonna mutate
fn sum(a: f64, b: f64) -> f64 {
return a + b;
}
fn sum(a: f64, b: f64) -> f64 {
a + b // last expression returns (no semicolon)
}
let answer = {
let y = 40;
let x = 2;
y + x
};
println!("The answer is {}", answer);
The answer is 42
Must satisfy all branches
fn main() {
let age = 23;
let description = match age {
x if x < 13 => "child",
13..=17 => "teen",
18..=59 => "adult",
x if x > 59 => "senior"
};
println!("{} is {}", age, description);
}
error[E0004]: non-exhaustive patterns: `std::i32::MIN..=12i32` and `60i32..=std::i32::MAX` not covered
--> src/main.rs:3:29
|
3 | let description = match age {
| ^^^ patterns `std::i32::MIN..=12i32` and `60i32..=std::i32::MAX` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
fn main() {
let age = 23;
let description = match age {
x if x < 13 => "child",
13..=17 => "teen",
18..=59 => "adult",
x if x > 59 => "senior",
_ => "Unknown"
};
println!("{} is {}", age, description);
}
23 is adult
#[allow(dead_code)]
enum TaskState {
Running,
Completed,
Failed
}
fn main() {
let state = TaskState::Completed;
let message = match state {
TaskState::Completed => "Completed",
_ => "Not completed or failed",
};
println!("{}", message);
}
#[allow(dead_code)]
enum Outcome {
Hex(String),
Number(i32),
}
fn main() {
let a = Outcome::Hex("FE".into());
let message = match a {
Outcome::Hex(hex_str) => format!("Hex string of {}", hex_str),
Outcome::Number(num) => format!("Number of {}", num),
};
println!("Outcome is {}", message);
}
Outcome is Hex string of FE
In fact Option and Result are Enums as well
enum Option<T> {
None,
Some(T),
}
enum Result<T, E> {
Ok(T),
Err(E),
}
No OOP, uses implementation and Traits
#[derive(Debug)]
struct Point3D {
x: f64,
y: f64,
z: f64,
}
fn main() {
let p = Point3D {
x: 10.0,
y: 5.2,
z: 6.5,
};
println!("Point in space: {:?}", p);
}
Point in space: Point3D { x: 10.0, y: 5.2, z: 6.5 }
#[derive(Debug)]
struct Point3D {
x: f64,
y: f64,
z: f64,
}
impl Point3D {
fn move_by(self, x: f64, y: f64, z: f64) -> Point3D {
Point3D {
x: self.x + x,
y: self.y + y,
z: self.z + z,
}
}
}
fn main() {
let p = Point3D {
x: 10.0,
y: 5.2,
z: 6.5,
};
let p2 = p.move_by(0.1, 0.2, 0.3);
println!("Point in space: {:?}", p2);
}
Point in space: Point3D { x: 10.1, y: 5.4, z: 6.8 }
A trait is a collection of methods defined for an unknown type: Self. They can access other methods declared in the same trait. Traits can be implemented for any data type.
Like an interface in other languages.
Traits can be added to existing structs, this extending and adding functionality and features.
Let's look at Rayon which provides lightweight data parallelism for Rust.
fn sum_of_squares(input: &[i32]) -> i32 {
input.iter()
.map(|&i| i * i)
.sum()
}
use rayon::prelude::*;
fn sum_of_squares(input: &[i32]) -> i32 {
input.par_iter() // <-- just change that!
.map(|&i| i * i)
.sum()
}
fn upper_limit_for_nth_prime(n: u32)-> usize {
if n < 6 {
return 100
}
let n = n as f64;
(n * (n.ln() + n.ln().ln())).ceil() as usize
}
/// Calculates nth prime (0 based) using
/// [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes)
pub fn nth(n: u32) -> u32 {
let limit = upper_limit_for_nth_prime(n) + 1;
let mut is_prime = vec![true; limit];
let mut total_found = 0;
for number in 2..limit {
if is_prime[number] {
if total_found == n {
return number as u32;
}
total_found += 1;
for num_mul in (number * number..limit).step_by(number) {
is_prime[num_mul] = false;
}
}
}
assert!(false, "Should not get here");
0
}
PyO3 supports Python 3.6 and up, PyPy. Allows calling from Rust to Python and vice versa.
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
/// A Python module implemented in Rust.
#[pymodule]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
setuptools-rust is a plugin for setuptools to build Rust Python extensions implemented with PyO3 or rust-cpython.
Maturin enables building and publishing crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages.
Thanks!
Questions?