Rust – Intro for C++ programmers



Rust – Intro for C++ programmers

1 0


rust-pottcpp

Rust presentation for C++ user group

On Github bkircher / rust-pottcpp

Rust

Intro for C++ programmers

Created by Kai / @_cibo_ and Ben / @predatorhat

fn main() {
    println!("Hello, there!");
}

rust-lang.org

play.rust-lang.org

  • No garbage collector
  • No big runtime
  • Automatic memory management
  • Zero-cost abstractions

Syntax

  • Imperative
  • C/C++-like: curly braces and semicolons as function and statement delimiter.
  • Freeform: indentation isn't significant
C++ Rust auto x = 42; let x = 42; std::unique_ptr<T> Box<T> std::shared_ptr<T> Rc<T> and Arc<T> Destructors trait Drop __asm asm! macro

Selling Point?

Quite similar to C++. So why do we need another language?

Selling Point?

Rust's type system!

void logError(const char* msg, int* ints) {
    fprintf(stderr, "%s: %i\n", msg, ints[0]);
}

int main() {
    int* ptr = (int*)malloc (SIZE);
    // ...
    if (err) {
        abrt = 1;
        free(ptr);
    }
    // ...
    if (abrt) {
        logError("operation aborted before commit", ptr);
    }
}
          

CWE-416: Use After Free

To catch this in C or C++ you need

  • Something like ASAN and/or Valgrind's Memcheck
  • Good test coverage
  • And you need to run those tests
  • If code is in one TU: static code analysis might help

In Rust, lifetime is part of an object's type

fn main() {
    let x;

    {
        let y = 5;
        x = &y;
    }

    println!("x's value is {}", x);
}
          

And thus, checked at compile time

Ownership

Move by default: variables are moved to new locations, preventing previous locations from using it.

There is only one owner of data!

Who is using Rust?

  • rustc
  • Mozilla Servo browser engine
  • Skylight, a profiler for Rails apps
  • MaidSafe, a platform for distributed applications

Libraries?

https://crates.io/

Crates & Modules

A Crate is the Rust equivalent to a C library or binary

rustc hello_world.rs
./hello_world
Hello, World!

A single source file defines one or more modules.

foo.rs:

mod foo {
  fn hello_world() {
    println!("Hello, World!");
  }

  mod bar {
    fn goodbye_world() {
      println!("Goodbye!");
    }
  }
}
foo::bar::goodbye_world();

A source file can reference other modules

main.rs:

mod foo;

fn main() {
  foo::hello_world();
}
rustc main.rs

Compiles main.rs and foo.rs into main executable.

A source file can reference external modules

rustc --crate-type dylib foo.rs

main.rs:

extern crate foo;

fn main() {
  foo::hello_world();
}
rustc main.rs

Compiles main.rs and links foo at run time.

Cargo

cargo new --bin program_name
[package]
name = "program_name"
version = "0.0.1"
authors = ["Kai Michaelis <kai.michaelis@rub.de>"]

[dependencies]
num = "~0.0.4"
slow_primes = "~0.1.4"

Downloads all dependencies and builds everything

cargo build

FFI

Calling C from Rust and vice versa

FFI

Call Rust code directly from C or C++

#[no_mangle]
pub extern fn hello_rust() -> *const u8 {
    "Hello, world!\0".as_ptr()
}

FFI

Call C code from Rust also pretty easy

int sqlite3_open(
    const char *filename,   /* Database filename (UTF-8) */
    sqlite3 **ppDb          /* OUT: SQLite db handle */
);

translates basically to

extern crate libc;
use libc::c_int,c_char;

#[link(name = "sqlite3")]
extern {
    pub fn sqlite3_open(filename: *const c_char,
                        ppDb: *mut *mut sqlite3) -> c_int;
}

FFI

You wouldn't do this yourself all the way: there is bindgen

FFI

And of course, you wouldn't do this with sqlite3 because there is already crates.io/crates/rusqlite

Pattern Matching & Deconstruction

Types in Rust

Rust C++ bool bool u8, u16, u32, u64 uint8_t, uint16_t, uint32_t, uint64_t i8, i16, i32, i64 int8_t, int16_t, int32_t, int64_t usize, isize uintptr_t, intptr_t f32, f64 float, double char 32 bit Unicode code point

Types in Rust (cont.)

Rust C++ str std::string, UTF-8 encoded (A, B, ...) std::tuple<A,B,...> [T; N] std::array<T,N> &[T] Pair of iterators type A = B using A = B struct A { ... } class A { ... } enum A { ... } boost::variant

Deconstruction

struct Employee {
    name: String,
    age: u8,
    department: String
}
let e1 = Employee {
    name:       "Kai Michaelis".to_string(),
    age:        29,
    department: "Engineering".to_string()
};

let Employee{ name: n, ..} = e1;

// Prints "Hello, I'm Kai Michaelis"
println!("Hello, I'm {}",n);
let e1 = Employee {
    name:       "Kai Michaelis".to_string(),
    age:        29,
    department: "Engineering".to_string()
};

if let Employee{ age: 67, ..} = e1 {
    println!("Time to retire!");
} else {
    println!("You still got {} years to go",67 - e1.age);
}

Enumerations

#[derive(PartialEq)]
enum Fruit {
    Apple = 1,
    Banana = 2,
    Kiwi,
    Pineapple
}
fn say_it(fruit: Fruit) {
   match fruit {
      Fruit::Apple => println!("Apple"),
      Fruit::Kiwi => println!("Kiwi"),
      Fruit::Pineapple => println!("Pineapple"),
   }
}
enum NumberOrText {
    Number(i32),
    Text(String)
}
fn print_number_or_text(nt: NumberOrText) {
    match nt {
        NumberOrText::Number(i) => println!("Number: {}",i),
        NumberOrText::Text(t) => println!("Text: {}",t)
    }
}
let a: NumberOrText = Number(42);
let b: NumberOrText = Text("Hello, World".to_string());

// Prints "Number: 42"
print_number_or_text(a);

// Prints "Text: Hello, World"
print_number_or_text(b);

Simple Tree Walking

use std::boxed::Box;
use std::ops::Deref;

enum Tree {
    Leaf(char),
    Node(Box<Tree>,Box<Tree>)
}

fn depth_first_search(root: &Tree) {
    match root {
        &Tree::Leaf(s) => println!("{}",s),
        &Tree::Node(ref left,ref right) => {
            depth_first_search(left.deref());
            depth_first_search(right.deref())
        }
    }
}

Simple Tree Walking (cont.)

fn main() {
    let tree =
      Box::new(Tree::Node(
          Box::new(Tree::Node(
              Box::new(Tree::Leaf('H')),
              Box::new(Tree::Node(
                  Box::new(Tree::Leaf('e')),
                  Box::new(Tree::Leaf('l')))))),
            Box::new(Tree::Node(
                Box::new(Tree::Node(
                    Box::new(Tree::Leaf('l')),
                    Box::new(Tree::Leaf('o')))),
                Box::new(Tree::Leaf('!'))))));

    // Prints "Hello!"
    depth_first_search(&tree);
}

Small Peek Into Error Handling

panic! unwinds the thread

fn guess(n: i32) -> bool {
    if n < 1 || n > 10 {
        panic!("Invalid number: {}", n);
    }
    n == 5
}

fn main() {
    guess(11);
}

std::option::Option

enum Option<T> {
    None,
    Some(T),
}

Option<T>

fn find(haystack: &str, needle: char) -> Option<usize> {
    for (offset, c) in haystack.char_indices() {
        if c == needle {
            return Some(offset);
        }
    }
    None
}

fn main() {
    let filename = "foobar.txt";
    match find(filename, '.') {
        Some(i) => println!("Filename extension: {}", &filename[i+1..]),
        None => println!("No extension found!"),
    }
}

Thoughts on Error Handling in Rust

Somewhat mixed...

Way better than arbitrary return values Seems harder to use than exceptions Anyone?
Rust Intro for C++ programmers Created by Kai / @_cibo_ and Ben / @predatorhat