Today, we're going to talk about

  • Rust as a C/C++ alternative
    • Build System
    • Type system
    • Data races
    • Traits vs C++ classes
    • Error handling
  • Real project examples
  • Conclusions

System Programming Languages

Build system

What should a good build system provide?

  • Compilation for different targets/platforms
  • Dependency resolution
  • Optional features
  • Custom build steps
  • Automated testing

  • ffmpeg: make
  • v8: GN, GYP
  • SpiderMonkey: autoconf
  • JavaScriptCore: Xcode
  • Chromium: ninja
  • Cocos2dx: cmake
  • Boost: b2, bjam
  • OpenSSL: custom
  • Blender: SCons
  • Others: Konan, Bazel

Rust Build System: Cargo

Cargo

Super easy to set up


[package]
name = "my-binary"
version = "0.1.0"

[dependencies]
uuid = "1.0"
my-library = { git = 'https://github.com/mortimer/my-library.git' }
my-renderer = { path = '../..//my-renderer' }
						

Super easy to build & run

Cargo

Multiple platform compilations for free


#Desktop							
cargo build
#Android
cargo build --target=armv7-linux-androideabi
#iOS
cargo build --target=aarch64-apple-ios
#WebAssembly
cargo build --target=wasm32-unknown-unknown
						

Development workflow


[replace]
# Compile project with a SpiderMonkey fork
mozjs_sys = {path = '/home/mortimer/Projects/mozjs_fork'}
						

Optional features in C++

  • Deep into the header code
  • Can't validate errors
  • Warning: #ifdef vs #define FEATURE_FLAG 0
  • No dependency resolution...

Optional features in C++

Optional features in Rust/Cargo

Easy & elegant set up


[features]
default = ["openvr", "mock"]
openvr = ["libloading"]
mock = []
googlevr = ["gvr-sys"]
oculusvr = ["ovr-mobile-sys"]
					

Easy & elegant optional code


#[cfg(feature = "oculusvr")]
mod oculusvr;
					

Ease & safe to use


[dependencies]
rust-webvr = {features = ["openvr", "googlevr", "oculusvr"], version = "0.9"}
					

Reliable platform detection in C++

Reliable platform detecion in Rust


#[cfg(target_os = "linux")]
fn platform_hello() {
  println!("You are running Linux!");
}
#[cfg(target_os = "android")]
fn platform_hello() {
  println!("You are running Android!");
}	
#[cfg(target_os = "ios")]
fn platform_hello() {
  println!("You are running iOS!");
}	
#[cfg(target_os = "macos")]
fn platform_hello() {
  println!("You are running MacOS!");
}	
					

C++ 'modules'


#include <string>
int main() {
	return 0;
}
					

Show preprocessor output


gcc -E test.cpp > result.text
					

22,020 lines!


template 
struct __attribute__ ((__type_visibility__("default"))) owner_less >
	: binary_function, weak_ptr<_Tp>, bool>
{
	typedef bool result_type;
	__attribute__ ((__visibility__("hidden"), __always_inline__))
	bool operator()( weak_ptr<_Tp> const& __x, weak_ptr<_Tp> const& __y) const
		{return __x.owner_before(__y);}
	__attribute__ ((__visibility__("hidden"), __always_inline__))
	bool operator()(shared_ptr<_Tp> const& __x, weak_ptr<_Tp> const& __y) const
		{return __x.owner_before(__y);}
	__attribute__ ((__visibility__("hidden"), __always_inline__))
	bool operator()( weak_ptr<_Tp> const& __x, shared_ptr<_Tp> const& __y) const
		{return __x.owner_before(__y);}
};
					

Problems of current C++ 'modules'

  • Compile-time scalability
  • Fragility: textual inclusion, macro
  • Conventional workarounds: include guard, pragma
  • Tool confusion: include orders, etc.

Tim Sweeney about C++17 Modules working draft

“I'm trying to understand the rationale for the oddity of the C++ modules TS”

Rust modules

Unit testing

C++: Research & set up testing framework


#include <gtest/gtest.h>

TEST(MyTestSuitName, MyTestCaseName) {
  int actual = 1;
  EXPECT_GT(actual, 0);
  EXPECT_EQ(1, actual) << "Should be equal to one";
}
					

Rust: Built-in Unit testing


#[test]
fn it_works() {
  assert_eq!(4, adder::add_two(2));
}
					

Run all tests


$) cargo test
Compiling adder v0.0.1 (file:///home/you/projects/adder)
	Running target/adder-91b3e234d4ed382a

running 1 test
test tests::it_works ... ok
					

Custom build steps in C++

  • Python
  • Confusing CMAKE COMMAND syntax
  • Custom

vs

Custom build steps Rust

  • Consistent with the language

fn main() {
  if let Ok(aar_out_dir) = env::var("AAR_OUT_DIR") {
	fs::copy(&Path::new("src/api/googlevr/aar/GVRService.aar"),
			 &Path::new(&aar_out_dir).join("GVRService.aar")).unwrap();

	fs::copy(&Path::new("src/api/oculusvr/aar/OVRService.aar"),
			 &Path::new(&aar_out_dir).join("OVRService.aar")).unwrap();
  }
}
					

Other modern build systems: Gradle

Other modern build systems: NPM

C++ coding style guide

C++ coding style guide: Mozilla

C++ coding style guide: Chromium

C++ coding style guide: Oculus

Rust coding style guide

  • One less thing to worry about
  • Consistency is very valuable

warning: variable `frameData` should have
a snake case name such as `frame_data`
					

Type system

  • Rust: super strict typing
  • C++: strict typing
  • JavaScript: untyped

Rust, C/C++, Javascript

C++ bad defaults: const and copies

Rust: good defaults

  • Inmutability & borrow/move semantics
  • Explicit copies: Vec::clone()

Rust compile time code safety

e.g.: Concurrency without data races

Rust compile time code safety

e.g.: Concurrency without data races

Rust compile time code safety

C++ experts don't make errors

C++ experts don't make errors

C++ experts don't make errors

C++ experts don't make errors

“std::string is responsible for almost half of all allocations in the Chrome browser process; please be careful how you use it!”

C++ experts don't make errors

Errors cost money

Errors can cost a company it's reputation

Metaprogramming & compile time code

C++ macros: Simple text substitution


#define FIVE_TIMES(x) 5 * x
int main() {
  printf("%d\n", FIVE_TIMES(2 + 3)); 
  return 0;
}
					

Variable captures


#define LOG(msg) do { \
  int state = get_log_state(); \
  if (state > 0) { \
    printf("log(%d): %s\n", state, msg); \
  } \
} while (0)
					

const char *state = "It's over 9000!";
LOG(state);
					

Rust hygienic macro system

macro expansion happens in a distinct syntax context


macro_rules! log {
  ($msg:expr) => {{
	let state: i32 = get_log_state();
	if state > 0 {
	  println!("log({}): {}", state, $msg);
	}
  }};
}		
					

Procedural & built-in macros


#[derive(Debug, Clone)]
pub struct VRFrameData {
  pub timestamp: f64,
  pub left_projection_matrix: [f32; 16],
  pub left_view_matrix: [f32; 16],
  pub right_projection_matrix: [f32; 16],
  pub right_view_matrix: [f32; 16],
  pub pose: VRPose,
}
					

println!("{:?}", frame_data);
					

Compile time code


constexpr factorial (int n){
	return n > 0 ? n * factorial( n - 1 ) : 1;
}	

constexpr int value = factorial(10);
println!("factorial: %d", value);
					
  • Macro is another language into the language
  • constexpr is very powerful, still limited in Rust.

Compile time string concatenation

Traits vs class hierarchies

Rust favors "composition over inheritanceā€¯

Traits vs class hierarchies

Traits vs class hierarchies

Traits vs class hierarchies

Class hierarchies

  • vtable vs trait pointers
  • Template alternative
  • C++ concepts

Error handling in C++: Exceptions

  • Handling exceptions is not enforced by the type-system
  • Dispatching to the catch block is orders of magnitude slower

Error handling in C++: Error codes

  • Error-codes are ancient and used everywhere
  • Functional composition is hard
  • Out-parameters different memory layout

Error handling in Rust

  • Option<T>, Result<T> Tuples, etc
  • Super powerful enums
  • C++ std::expected

Conclussions

  • Is Rust a valid C/C++ alternative?
  • Is Rust ready to use for real projects?
  • Is Rust difficult to learn?
  • Rust vs C++ tooling?
  • Slow compilation times?

Thank you!

@MortimerGoro