Variables

C++

Type Inference

C++11 has type inference, previous versions of C++ do not. Type inference allows the programmer to assign a value to an auto typed variable and let the compiler infer the type based on the assignment.

Boolean and numeric types are fairly easy to understand providing the code is as explicit as it needs to be.

auto x = true; // bool
auto y = 42;   // int
auto z = 100.; // double

Where C++ gets messy is for arrays and strings. Recall that strings are not primitive types in the strong sense within C or C++ so auto requires they be explicitly defined or the type will be wrong.

auto s = std::string("Now is the window of our discontent"); // char string
auto s = U"Battle of Waterloo"; // char32_t pointer to UTF-32 string literal

Strings are covered elsewhere, but essentially there are many kinds of strings and C++/C has grown a whole bunch of string prefixes to deal with them all.

Arrays are a more interesting problem. The auto keyword has no easy way to infer array type so is one hack workaround to assign a templatized array to an auto and coerce it.

template <typename T, int N> using raw_array = T[N];
auto a = raw_array<int, 5>{};

Rust

Rust, variables are declared with a let command. The let may specify the variable's type, or it may also use type inference to infer it from the value it is assigned with.

let x = true; // x: bool
let y = 42; // y: i32
let z = 100.0; // z: f64
let v = vec![10, 20, 30]; // v: Vec<i32>
let s = "Now is the winter of our discontent".to_string(); // s: String
let s2 = "Battle of Waterloo"; // s2: &str
let a1: [i32; 5] = [1, 2, 3, 4, 5];

Rust has no problem with using type inference in array assignments:

let a2 = ["Mary", "Fred", "Sue"];

Note that all array elements must be the same type, inference would generate a compiler error for an array like this:

// Compile error
let a3 = ["Mary", 32, true];

Scope rules

Scope rules in C, C++ and Rust are fairly similar - the scope that you declare the item determines its lifetime.

Shadowing variables

One very useful feature of Rust is that you can declare the same named variable more than once in the same scope or nested scopes and the compiler doesn't mind. In fact you'll use this feature a lot.

This is called shadowing and works like this:

let result = do_something();
println!("Got result {:?}", result);
if let Some(result) = result {
  println!("We got a result from do_something");
}
else {
  println!("We didn't get a result from do_something");
}

let result = do_something_else();
//...

This example uses the variable name result 3 times. First to store the result of calling do_something(), then to extract some value Foo from Option<Foo> and a third time for calling something else. We could have assigned result to result2 and then later on assigned the value do_something_else() to result3 but we didn't need to because of shadowing.

Pointers

In C++

A pointer is a variable that points to an address somewhere in memory. The pointer's type indicates to the compiler what to expect at the address but there is no enforcement to ensure that the address actually holds that type. A pointer might might be assigned NULL (or nullptr in C++11) or may even be garbage if nothing was assigned to it.

char *name = "David Jones";

int position = -1;
find_last_index("find the letter l", 'l', &position);

Generally pointers are used in situations where references cannot be used, e.g. functions returning allocated memory or parent / child collection relationships where circular dependencies would prevent the use of references.

C++11 deprecates NULL in favour of new keyword nullptr to solve a problem with function overloading.

void read(Data *data);
void read(int value);
// Which function are we calling here?
read(NULL);

Since NULL is essentially #define NULL 0 and 0 is an integer, we call the wrong function by accident. So C++ introduces an explicit nullptr for this purpose.

read(nullptr);

In Rust:

Rust supports pointers, normally called raw pointers however you will rarely use them unless you need to interact with C API or similar purposes.

A pointer looks fairly similar to that of C++:

// This is a reference coerced to a const pointer
let age: u16 = 27;
let age_ptr: *const u16 = &age;

// This is a mut reference coerced to a mutable pointer
let mut total: u32 = 0;
let total_ptr: *mut u32 = &mut total;

Although you can make a pointer outside of an unsafe block, many of the functions you might want to perform on pointers are unsafe by definition and must be inside unsafe blocks.

The documentation in full is here.

References

In C++

A reference is also a variable that points to an address but unlike a pointer, it cannot be reassigned and it cannot be NULL. Therefore a reference is generally assumed to be safer than a pointer. It is still possible for the a reference to become dangling, assuming the address it referenced is no longer valid.

In Rust

A reference is also lifetime tracked by the compiler.

Tuples

A tuple is list of values held in parenthesis. They're useful in cases where transient or ad-hoc data is being passed around and you cannot be bothered to write a special struct just for that case.

In C++

C++ does not natively support tuples, but C++11 provides a template for passing them around like so:

#include <tuple>

std::tuple<int, int> get_last_mouse_click() {
  return std::make_tuple(100, 20);
}

std::tuple<int, int> xy = get_last_mouse_click();
int x = std::get<0>(xy);
int y = std::get<1>(xy);

In Rust

Tuples are part of the language and therefore they're far more terse and easy to work with.

fn get_last_mouse_click() -> (i32, i32) {
  (100, 20)
}
// Either
let (x, y) = get_last_mouse_click();
println!("x = {}, y  = {}", x, y);
// or
let xy = get_last_mouse_click();
println!("x = {}, y  = {}", xy.0, xy.1);

results matching ""

    No results matching ""