The year 1979 saw Danish computer scientist, ‘Bjarne Stroustrup’ working on a project of C language, that he called ‘C with Classes’. He enriched C language by introducing new features like classes, derived classes, default arguments, and inlining to the C compiler. 14 years later, ‘C with Classes’ was labeled ‘C++’ with the addition of virtual functions, operator overloading, references, etc.
Fast forward to 2016, C++ continues to be the go-to language of modern-day programmers for developing efficient, flexible and high-performance desktop applications, system servers, and especially critical software for use in space probes and satellites.
While C++ is a ‘general purpose’, object-oriented, low-level memory consuming programming language that came out for the development of large embedded systems; Rust, released in May 2015 (stable version Rust 1.0), rolled out as a system programming language that is ultra-fast, safe, and multi-threaded. Rust has been designed to aid in the construction of systems that are highly concurrent. Rust achieves this with its rich feature set of safety and controlled memory layout.
Let us have a look at how Rust is different from C++ while also being similar to it.
Rust inherits its syntax from C and C++, however not all that is in C or C++ is borrowed as is. That is, while it uses the keywords ‘if’, ‘else’, ‘for’ and ‘while’ from them, it introduces few of its own like ‘Match’ for multi-pronged branching. While syntactically Rust is similar to C++, semantically it is a different world altogether.
C++ however, inherits most of its syntax from C. However, it too, by introducing OOP features to C classes made C programmers familiar with concepts of abstraction, encapsulation, and inheritance in C++. The standard library of C++ includes vectors, maps, lists, queues, tuples, regular expressions, and other such features that were alien to programming languages of the 20th century.
While most data types in Rust are similar to their counterparts in C++, there’s one stark difference between them, that is, data and the way that data behaves is strictly separated in Rust. Simply saying, Rust uses functions, traits, and implementations for defining data behavior, but does not let them define data, much like Java interfaces.
Structures in Rust are similar to C++ structures minus methods i.e. the list of declared fields. ‘.’ The operator is used to accessing fields in structures, just like C++. However, owing to their value semantics, Rust does not allow recursive structures.
Tuples, as most programmers would recall from their C++ experience are data sequences of anonymous nature. They’re nameless creatures and are accessed via their structure. While parenthesized types in the sequence are used for their declaration.
Enums are data types that can contain multiple values. Enums in Rust is more efficient and strong then they’re in C++, they also define as a list of types. Rust uses Enums to obtain Object-Oriented Polymorphism.
Rust inherits its syntax mostly from C++ and uses the same logical and arithmetic operators. While the basic concept of integers is the same as that in C++, the syntax is a bit different i.e. ‘int’ defines an integer while ‘uint’ tells of an unsigned integer. It also uses data types that are of an explicit size, i.e. ‘u8’ is an unsigned 8bit integers while ‘i32’ is a signed integer of 32bit. Rust, unlike C++, is flexible enough to let us use a suffix to indicate/access numeric literals i.e. using ‘i’ instead of ‘int’. In case no suffix is given, it tries to infer the data types of literals involved and assign them ‘int’ or ‘f64’ (for decimals) data types if fails to identify their type.
Numerical Operators (+, -, *, /, %), Bitwise operators (|, &, ^, <<, >>), Comparison operators (==, !=, >, <, >=, <=) and short-circuit logical operators (||, &&) are all similar in both languages. However, Rust is quite strict when it comes to applying operator on data types i.e. it ensures that bitwise operators be applied only with integers, while the logical operators must only be applied on Booleans. Rust also does not have increment or decrement operators i.e. – – and ++.
Other than mandatory braces, usage of If statement in Rust is similar to one in C++, while braces around the test expression are not essential.
Rust uses the same syntax for While loop, but lacks a ‘Do…While loop’. For loops, however, are a bit different in Rust. It uses keyword ‘all’ to do most work in a loop i.e. to print a vector of integers it simply uses condition ‘all.iter()’ to iterate each item, instead of using conventional for loop structure.
Match expression is a powerful alternative for C++ Switches in Rust. Rust compiler ensures that you don’t forget a break with it, and in case you add a case to enum the compiler will ensure it is covered by your match statement. Consider the following code snippet for example:
fn print_alpha(x: i32) { match y { 0 => println!("A is zero"), 1 => println!("B is one"), 10 => println!("C is ten"), z => println!("Z is something else {}", k), } }
Note that we use, ‘=>’ operator to travel from matched value to expression for execution, while the match arms are separated by a comma ‘,’. Coming to semantic differences: the matched patterns need to be exhaustive, i.e. all possible values of the matched expression (y in the above example) must be covered. In the last line, z is bound to the value being matched (k in this case).
Arrays in Rust are different than arrays in C++, i.e. in Rust Arrays can be both dynamic and static. While both these types, primarily, offer fixed size length arrays; Rust also provides programmers with growable Arrays called ‘Vec’.
Alike C++, Array indexing is zero-based, but in order to ensure Rust is a safer language than C/C++; all access to Arrays is bound checked. However, one can still have unchecked access to Rust via using the ‘get_unchecked’ array method.
Slices are arrays of unknown length (at compile time). Their declaration is the same as arrays, except they’re without a known length. But since compiler in Rust works differently than in C++, hence in order to use slices it’s compulsory for programmers to have pointers for slices. This is mainly achieved via coercion i.e. from fixed-length arrays to slices.