A Love Letter 💌 to
Rainer Stropek | @rstropek
Introduction
Rainer Stropek
- Passionate software developers for 25+ years
- Microsoft MVP, Regional Director
- Trainer, Teacher, Mentor
- 💕 community
Relationship to Languages
Image by
Francis C. Franklin / CC-BY-SA-3.0
Source (Wikimedia)
Attractive?
node_modules anybody?
-
No undefined behavior [...], including memory safety and the absence of data races.
-
Stricter type system for further reduction of logic errors.
-
A clear distinction between safe and `unsafe` code.
-
Featureful language [...]
-
Extensive freestanding standard library [...]
-
Integrated out of the box tooling [...]
Why Rust?
Overall, Rust is a language that has successfully leveraged decades of experience from system programming languages as well as functional ones [...] (source)
Rust is an intriguing language. It closely resembles C++ in many ways, hitting all the right notes [...]. [...] it also has the potential to solve some of the most vexing issues that plague C++ projects [...]. (source)
Rust provides memory safety guarantees [...] and runtime checks to ensure that memory accesses are valid. This safety is achieved while providing equivalent performance to C and C++. (source)
For developers, Rust offers the performance of older languages like C++ with a heavier focus on code safety. Today, there are hundreds of developers at Facebook writing millions of lines of Rust code. (source)
The Rust Foundation is an independent non-profit organization to steward the Rust programming language and ecosystem, with a unique focus on supporting the set of maintainers that govern and develop the project. (source)
Image by
Francis C. Franklin / CC-BY-SA-3.0
Source (Wikimedia)
- Safety
- Performance
- Tooling
- Sponsoring
- Community
Rust is attractive
Safety, Stability
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
char *getData() {
char *buffer = (char*)malloc(1024);
strcpy(buffer, "Hi!\n"); // read data from somewhere
return buffer;
}
void getAndProcessData() {
char *buffer = getData();
cout << buffer;
}
int main() {
getAndProcessData();
}
Problem: Memory Leak
-
See also CWE-401 Vulnerability
-
Try online (repl.it)
-
This is not best-practice C++ code, just for demo purposes. Use types like vector or string, RAII-techniques like shared pointers, etc. instead.
#include <iostream>
#include <string.h>
#include <stdlib.h>
using namespace std;
bool execSql(const char *stmt) { return false; }
void logError(const char *category, const char *details) {
cout << category << " (" << details << ')';
}
int main() {
char* stmt = (char*)malloc(100);
strcpy(stmt, "INSERT INTO ...");
bool errorHappened = false;
if (!execSql(stmt)) {
errorHappened = true;
free((void*)stmt);
}
// Do something else
if (errorHappened) {
logError("Error executing SQL", stmt);
}
}
Problem: Use After Free
-
See also CWE-416 Vulnerability
using System;
class SqlConnection : IDisposable { ... }
...
OpenConnection();
bool errorHappened = false;
try {
ExecSql(Connection, "INSERT INTO ...");
// Continue working with DB
}
catch {
errorHappened = true;
Connection.Dispose();
}
// Do something else
if (errorHappened) {
LogError("Error executing SQL", Connection);
}
Problem: Use After Free
Managed Language (C#)
Try online (repl.it)
#include <iostream>
using namespace std;
void printValue(const int *p) {
cout << *p << '\n'; // Print value on screen
delete p; // Cleanup by freeing p
}
int main() {
int* p = new int; // Allocate and init
*p = 42;
printValue(p); // Call method to print value
delete p; // Cleanup by freeing p
cout << "done.\n";
}
Problem: Double Free
using System;
using System.Buffers;
class MainClass {
public static void Main(string[] args) {
var numbers = ArrayPool<int>.Shared.Rent(4);
try { DoSomethingWithNumbers(numbers); }
finally { ArrayPool<int>.Shared.Return(numbers); }
}
public static void DoSomethingWithNumbers(int[] numbers) {
Task.Run(async () => {
// Do something with Numbers
ArrayPool<int>.Shared.Return(numbers);
});
}
}
Problem: Double Free
Managed Language (C#)
My Rules
- Each value in Rust has a variable that is called its owner
- There can only be one owner at a time
- When the owner goes out of scope, the value will be dropped
Ownership rules enforced by compiler
fn main() {
let numbers = vec![1, 1, 2, 3, 5, 8];
let other_numbers = numbers;
println!("{:?}", other_numbers);
}
Moving Ownership
fn main() {
let numbers = vec![1, 1, 2, 3, 5, 8];
let other_numbers = numbers;
println!("{:?}", other_numbers);
println!("{:?}", numbers);
}
numbers owns vector
Ownership change
Not allowed
Free vector ONCE
Try online (repl.it)
Moving Ownership
fn main() {
// Allocate array on heap
let numbers = vec![1, 2, 3, 5, 8];
println!("{:?}", numbers);
// Move ownership to other_numbers
let other_numbers = numbers;
println!("{:?}", other_numbers);
// Now we cannot access numbers anymore
// because value was moved.
// println!("{:?}", numbers); // -> does not COMPILE
// Make a (deep) copy -> no move of ownership
let cloned_numbers = other_numbers.clone();
println!("clone = {:?}, source = {:?}", cloned_numbers, other_numbers);
}
Clone -> 2 owners
Try online (repl.it)
Ownership and Functions
fn main() {
let numbers = vec![1, 2, 3, 5, 8];
consume(numbers); // Gives ownership to `consume`
let produced_numbers = produce(); // Takes ownership
println!("{:?}", produced_numbers);
// produced_numbers gets out of scope -> free memory
}
fn consume(numbers: Vec<i32>) {
let sum: i32 = numbers.iter().sum();
println!("The sum is {}", sum);
// numbers gets out of scope -> free memory
}
fn produce() -> Vec<i32> {
let mut numbers: Vec<i32> = Vec::new();
for i in 0..4 { numbers.push(i); }
numbers // Gives ownership to caller
}
numbers owns vector
Try online (repl.it)
Ownership change
Free vector
numbers owns vector
Free vector
Ownership change
Borrowing
fn main() {
let mut numbers = vec![1, 2, 3, 5, 8];
println!("The sum is {}",
consume(&numbers)); // Passes reference, keeps ownership
println!("The sum is {}",
add_and_consume(&mut numbers)); // Mutable reference, keeps ownership
println!("{:?}", numbers);
}
fn consume(numbers: &Vec<i32>) -> i32 {
// numbers is read-only, cannot be mutated
//numbers.push(42); // -> does NOT COMPILE
let sum: i32 = numbers.iter().sum();
sum
}
fn add_and_consume(numbers: &mut Vec<i32>) -> i32 {
numbers.push(42);
consume(numbers)
}
Try online (repl.it)
Borrows vector
Borrows mutable vec
Free vector
Is still owner
Broken Iterator
use futures::executor::block_on;
use futures_timer::Delay;
use std::time::Duration;
fn main() {
block_on(async_main());
}
async fn async_main() {
let mut numbers = vec![1, 2, 3, 5, 8];
let sum_future = sum(&numbers);
add(&mut numbers);
println!("The sum is {}", sum_future.await);
}
fn add(numbers: &mut Vec<i32>) {
numbers.push(42);
}
async fn sum(numbers: &Vec<i32>) -> i32 {
let iter = numbers.iter();
Delay::new(Duration::from_secs(2)).await;
iter.sum()
}
using System;
using static System.Console;
using System.Collections.Generic;
using System.Threading.Tasks;
class MainClass {
public static void Main (string[] args) {
var numbers = new List<int> {1,2,3,5,8};
var sumTask = SumAsync(numbers);
numbers.Add(13);
WriteLine($"The sum is {sumTask.Result}");
}
static async Task<int> SumAsync(
IEnumerable<int> numbers) {
var sum = 0;
foreach (var n in numbers) {
await Task.Delay(10);
sum += n;
}
return sum;
}
}
Compile-time error
Runtime error
Rust
C#
Borrowing
use std::time::Duration;
use std::sync::mpsc;
use std::thread;
fn main() {
let (sender, receiver) = mpsc::channel::<i32>();
thread::spawn(move || {
for i in 0..5 {
sender.send(i).unwrap();
thread::sleep(Duration::from_millis(500));
}
});
loop {
match receiver.recv() {
Ok(result) => println!("Received: {}", result),
Err(_) => {
println!("Done!");
break;
}
};
}
}
Try online (repl.it)
Receiver ownership
Move sender owner
- Clear ownership rules
- Enforced by compiler
- Less runtime errors
- Less vulnerabilities
Rust is stable and save
Deepness
Structs
struct Point {
x: f32,
y: f32,
}
struct Rect {
left_upper: Point,
right_lower: Point,
}
struct Circle {
center: Point,
radius: f32,
}
fn main() {
// Create an instance of Point on the stack
let p1 = Point { x: 20f32, y: 20f32 };
// Create another Point and take some values from p1
let p2 = Point { x: 21f32, ..p1 };
// By default, variables are immutable.
// Let's create a mutable rect.
let mut r = Rect { left_upper: p1, right_lower: p2 };
r.right_lower.y = 40f32;
// Create a Circle on the heap
let mut c = Box::new(
Circle { center: Point { x: 10f32, y: 10f32 },
radius: 42f32 });
c.radius = 10f32;
}
Functions & Structs
let mut r = Rect { ... };
r.enlarge(2.0f32);
println!("The area is {}",
c.area());
impl Rect {
fn width(&self) -> f32 {
self.right_lower.x - self.left_upper.x
}
fn height(&self) -> f32 {
self.right_lower.y - self.left_upper.y
}
fn enlarge(&mut self, factor: f32) {
self.right_lower.x = self.left_upper.x + self.width() * factor;
self.right_lower.y = self.left_upper.y + self.height() * factor;
}
fn area(&self) -> f32 {
self.width() * self.height()
}
}
impl Circle {
fn unit_circle() -> Self {
Circle { center: Point { x: 0f32, y: 0f32 }, radius: 1f32 }
}
fn area(&self) -> f32 {
consts::PI * self.radius * self.radius
}
}
let u = Circle::unit_circle();
println!("The area is {}",
u.area());
Traits
trait Area {
fn area(&self) -> f32;
}
impl Area for Rect {
fn area(&self) -> f32 {
self.width() * self.height()
}
}
impl Area for Circle {
fn area(&self) -> f32 {
consts::PI * self.radius * self.radius
}
}
let mut r = Rect {...};
let mut c = Box::new(Circle {...});
...
let shape_with_area: &dyn Area = &r;
println!("The area is {}",
shape_with_area.area());
let mut shapes = Vec::<&dyn Area>::new();
shapes.push(&r);
shapes.push(c.as_ref());
for s in shapes {
println!("The area is {}", s.area());
}
Traits
impl Add<Point> for Point {
type Output = Self;
fn add(self, other: Point) -> Self {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
impl Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{{ x: {}, y: {} }}", self.x, self.y)
}
}
let origin = Point { x: 20f32, y: 20f32 };
println!("{}", origin);
let other = origin + Point { x: 20f32, y: 20f32 };
println!("{}", other);
Macros❗
#[derive(Debug)]
struct Point {
x: f32,
y: f32,
}
let points = vec![
Point { x: 20f32, y: 20f32 },
Point { x: 21f32, y: 21f32 }
];
println!("{:?}", &points);
macro_rules! return_status {
($status:literal) => {
{
println!("Status: {}\n", $status);
return;
}
};
}
macro_rules! get_param {
($status:literal) => {
{
match std::env::args().filter(|a| a.starts_with($status)).nth(0) {
Some(p) => match p[$status.len()..].parse() {
Ok(p) => p,
Err(_) => return_status!(400)
},
None => return_status!(400)
}
}
};
}
fn main() {
println!("Content-Type: text/html");
// Get parameters from args (=requested date)
let year = get_param!("year=");
let month = get_param!("month=");
let day = get_param!("day=");
let str = match get(URL) {
Ok(d) => d,
Err(_) => return_status!(404)
};
//...
}
Enums
struct Raise {
current_pot: f32,
raise: f32,
}
enum PokerMove {
Fold,
Call(f32), // Note that enums can have values
Raise(Raise)
}
let poker_move = PokerMove::Raise(Raise { current_pot: 42f32, raise: 2f32 });
match poker_move {
PokerMove::Call(x) => println!("Player calls at pot size {}", x),
PokerMove::Raise(x) => println!("Player raises, pot size now {}", x.current_pot + x.raise),
_ => println!("Player folds"),
}
Enums - Option and Result
let moves = vec![
PokerMove::Fold,
PokerMove::Call(2f32),
PokerMove::Call(2f32),
PokerMove::Raise(Raise { current_pot: 42f32, raise: 2f32 })
];
let poker_move: Option<&PokerMove> = moves.iter().nth(4);
match poker_move {
Some(_) => println!("Found a move"),
None => println!("This move does not exist"),
};
let contents: Result<String, std::io::Error> = fs::read_to_string("main.rs");
match contents {
Result::Ok(data) => println!("{}", data),
Result::Err(err) => println!("Error: {}", err),
};
- Built-in types
- Structs
- Enums
- Traits
- Macros
Powerful, feature-rich type system
Treats
Mutability, Shadowing, Freezing
pub fn try_from<T: AsRef<str>>(location: T) -> Result<BoardIndex, &'static str> {
let location = location.as_ref().as_bytes(); // Note shadowing
...
}
fn main() {
let mut _some_value = 42i32;
{
let _some_value = _some_value; // Note shadowing by immutable version
// _some_value = 43i32; --> does not compile
}
_some_value = 43i32; // --> This is fine
}
Error Propagation❓
fn read_data() -> Result<String, io::Error> {
let mut f = File::open("data.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
Docs, Testing
/// Runs a given closure in parallel on all available CPUs...
///
/// # Examples
///
/// ```
/// ...
/// let result = mth_calc::run_on_all_cpus::<f32>(|| {
/// ...
/// }, 0f32, |x, y| { x + y });
///
/// let avg = result.0 / (result.1 * 10) as f32;
/// # if avg < 0f32 || avg >= 1f32 {
/// # panic!();
/// # }
/// println!("The average random number was {}", avg);
/// ```
pub fn run_on_all_cpus<T: Send + 'static>(body: fn() -> T, initial: T,
agg: fn(T, T) -> T) -> (T, u32) {
...
}
#[cfg(test)]
mod tests {
...
#[test]
fn it_calculates_result_correctly() {
...
}
}
Type Inference
fn main() {
let mut my_vec = Vec::new(); // Compiler does not know type of vector yet
my_vec.push(42i32); // Now type has become clear, it is i32
// my_vec.push("42"); -> does not compile because of type mismatch
println!("{:?}", my_vec);
}
There would be so much more...
Got curious?
Want to learn more?
A Love Letter 💌 to
Thank you for attending!
Rainer Stropek | @rstropek
Love Letter to Rust
By Rainer Stropek
Love Letter to Rust
Rust is exciting. The language promises zero-cost abstractions without the stability and security problems often seen with C and C++. Given that, Rust is gaining popularity in the area of systems programming – but it is not limited to that. Rust is a great choice for application development, too. You can use it to build blazingly fast web APIs in the cloud or enhance web apps using Webassembly. In this talk, Rainer Stropek, founder of the Rust Linz Meetup, will talk about the reasons of his excitement for Rust. You will here about some unique properties of the language and its related tooling that distinguishes Rust from other programming platforms. If you have not found the time to look into Rust but you are curious about it, this is the session for you.
- 1,626