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

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