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?

Rust, at 26%, is the most frequently used language
[for WebAssembly] (source)

The goal of this project is to add support for the Rust language to the Linux kernel. (source)

Most 💕 language in
Stackoverflow Survey 2016-2021 (source)

  • 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

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