CC-BY-SA This work is licensed under a Creative Commons
Attribution-ShareAlike 4.0 International License

Source: codeberg.org/andybalaam

Rust for the recalcitrant C++ programmer

CB Bailey (they/them)
Bloomberg
Andy Balaam (he/him)
element.io

Contents

Isn't C++ enough?

Types

Types

Types

struct MyService { unique_ptr<MyResource> res_; void shutdown() { res_ = nullptr; } int gather_stats() { return res_->stats(); } };

Types

struct MyService { res: Option<MyResource>, } impl MyService { fn shutdown(&mut self) { self.res = None; } fn gather_stats(&self) -> usize { self.res.stats() } }

Types

error[E0599]: no method named `stats` found for enum `Option` in the current scope --> src/main.rs:26:18 | 26 | self.res.stats() | ^^^^^ method not found in `Option` | note: the method `stats` exists on the type `MyResource` --> src/main.rs:10:5 | 10 | fn stats(&self) -> usize { | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `Option::expect` to unwrap the `MyResource` value, panicking if the value is an `Option::None` | 26 | self.res.expect("REASON").stats() | +++++++++++++++++

Types

fn gather_stats(&self) -> usize { if let Some(r) = self.res { r.stats() } else { 0 } }

Types

Types

#[derive(Clone, Debug, Serialize)] struct MyObject { n: u64, s: String, }

Types

let obj2 = obj.clone(); println!("{:?}", obj); serde_json::to_string(&obj);

Ownership

Ownership

Ownership

MyObject obj{3}; vector<MyObject> vec; vec.push_back(std::move(obj)); cout << obj.x << "\n";

Ownership

class Blob { private: std::string data_; public: explicit Blob(const char *); const char *data() const; }; std::string_view oops() { Blob b("I wonder how this works\n"); return b.data(); }

Ownership

let obj = MyObject { x: 3 }; let mut vec = Vec::new(); vec.push(obj); println!("{}", obj.x);

Ownership

error[E0382]: borrow of moved value: `obj` --> src/main.rs:17:20 | 9 | let obj = MyObject { x: 3 }; | --- move occurs because `obj` has type `MyObject`, which does not implement the `Copy` trait ... 13 | vec.push(obj); | --- value moved here ... 17 | println!("{}", obj.x); | ^^^^^ value borrowed here after move

Immutability

Immutability

Immutability

struct Parent { unique_ptr<Child> child; }; int main() { const Parent parent{unique_ptr(new MyChild{1})}; parent.child->set_age(10); }

Immutability

struct Parent { child: Box<dyn Child>, } fn main() { let parent = Parent { child: Box::new(MyChild { age: 1 }), }; parent.child.set_age(10); }

Immutability

error[E0596]: cannot borrow `*parent.child` as mutable, as `parent` is not declared as mutable --> src/main.rs:34:5 | 28 | let parent = Parent { | ------ help: consider changing this to be mutable: `mut parent` ... 34 | parent.child.set_age(10); | ^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

Enum types

Enum types

Enum types

enum IpAddress { V4([u8; 4]), V6([u16; 6]), }

Enum types

fn print_ip(addr: IpAddress) { match addr { IpAddress::V4(a) => println!( "{}.{}.{}.{}", a[0], a[1], a[2], a[3] ), IpAddress::V6(b) => println!( "{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", b[0], b[1], b[2], b[3], b[4], b[5] ), } }

Enum types in C++

struct V4 { unsigned char addr[4]; }; struct V6 { unsigned short addr[6]; }; using IpAddress = std::variant<V4, V6>;

Enum types in C++

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; void printAddress(const IpAddress& address) { std::visit(overloaded { [](const V4& arg){ std::cout << arg.addr[0] /* << ... */; }, [](const V6& arg){ std::cout << arg.addr[0] /* << ... */; } }, address); }

Enum types

Enum types

fn tricky_thing(input: i32) -> Result<(), String> { if input > 100 { Err(String::from("Too big!")) } else { Ok(()) } }

Enum types

fn do_something_tricky(input: i32) -> Result<i32, String> { tricky_thing(input)?; second_thing(); Ok(input * 2) }

Enum types

fn do_something_tricky(input: i32) -> Result<i32, String> { let res = tricky_thing(input); match res { Err(e) => return Err(e), Ok(r) => r, }; second_thing(); Ok(input * 2) }

Enum types response in C++

using VoidResult = std::expected<void, std::string>; VoidResult tricky_thing(int input) { if (input > 100) return std::unexpected("Too big!"); else return VoidResult(); }

Enum types response in C++

using IntResult = std::expected<int, std::string>; IntResult do_something_tricky(int input) { VoidResult r = tricky_thing(input); if (!r) { return std::unexpected(r.error()); } second_thing(); return input * 2; }

Concurrency

Concurrency

Concurrency

vector<thread> threads; int counter = 0; for (int i = 0; i < 10000; ++i) { threads.push_back(thread([&counter]{++counter;})); } for (thread& t: threads) { t.join(); } cout << "Total: " << counter << "\n";

Concurrency

let mut threads = Vec::new(); let mut counter = 0; for _i in 0..10000 { threads.push(thread::spawn(|| counter += 1)); } for thread in threads { thread.join().expect("Join failed"); } println!("Total: {}", counter);

Concurrency

error[E0499]: cannot borrow `counter` as mutable more than once at a time --> src/main.rs:11:36 | 11 | threads.push(thread::spawn(|| counter += 1)); | --------------^^-------------- | | | | | | | borrows occur due to use of `counter` in closure | | `counter` was mutably borrowed here in the previous iteration of the loop

Concurrency

let mut threads = Vec::new(); let counter = Arc::new(Mutex::new(0)); for _i in 0..10000 { let c = counter.clone(); threads.push(thread::spawn(move || *c.lock().unwrap() += 1)); } for thread in threads { thread.join().expect("Join failed"); } println!("Total: {}", *counter.lock().unwrap());

Packages

Packages

Packages

The upshot:

Packages

Downsides

[1] But see https://blessed.rs for recommended crates

Concepts

Concepts

The Verdict