C/C++ vs. Rust: A developer’s perspective

C/C++ vs. Rust: A developer’s perspective

In this post, you'll see the difference between Rust and C/C++ in a developer’s perspective

Originally published by Maourice Gonzalez at https://www.onmsft.com

C++ is an incredibly fast and efficient programming language. Its versatility knows no bounds and its maturity ensures support and reliability are second to none. Code developed in C++ is also extremely portable, all major operating systems support it. Many developers begin their coding journey with the language, and this is no coincidence. Being object-oriented means it does a very good job of teaching concepts like classes, inheritance, abstraction, encapsulation and polymorphism. Its concepts and syntax can be found in modern languages like C#, Java and Rust. It provides a great foundation that serves as a high speed on ramp to the more popular, easier to use and modern alternatives.

Now it’s not all rosy. C++ has a very steep learning curve and requires developers to apply best practices to the letter or risk ending up with unsafe and/or poor performing code. The small footprint of the standard library, while most times considered a benefit, also adds to the level of difficulty. This means successfully using C++ to create useful complex libraries and applications can be challenging. There is also very little offered in terms of memory management, developers must do this themselves. Novice programmers could end up with debugging nightmares as their lack of experience leads to memory corruption and other sticky situations. This last point has lead many companies to explore fast performing, safe and equally powerful alternatives to C++. For today’s Microsoft that means Rust.

The majority of vulnerabilities fixed and with a CVE [Common Vulnerabilities and Exposures] assigned are caused by developers inadvertently inserting memory corruption bugs into their C and C++ code - Gavin Thomas, Microsoft Security Response Center

Rust began as a personal project by a Mozilla employee named Graydon Hoare sometime in 2006. This ambitious project was in pre-release development for almost a decade, finally launching version 1.0 in May 2015. In what seems to be the blink of an eye it has stolen the hearts of hordes of developers going as far as being voted the most loved language four years straight since 2016 in the Stack Overflow Developer Survey.

The hard work has definitely paid off. The end result is very efficient language which is characteristically object oriented. The fact that it was designed to be syntactically similar to C++ makes it very easy to approach. But unlike the aforementioned it was also designed to be memory safe while also employing a form of memory management without the explicit use of garbage collection.

The ugly truth is software development is very much a trial and error endeavor. With that said Rust has gone above and beyond to help us debug our code. The compiler produces extremely intuitive and user friendly error messages along with great direct linking to relevant documentation to aid with troubleshooting. This means if the problem is not evident, most times the answer is a click away. I’ve found myself rarely having to fire up my browser to look for solutions outside of what the Rust compiler offers in terms of explanation and documentation.

Rust does not have a garbage collector but most times still allocates and release memory for you. It’s also designed to be memory safe, unlike C++ which very easily lets you get into trouble with dangling pointers and data races. In contrast Rust employs concepts which help you prevent and avoid such issues.

There are many other factors which have steered me away from C++ and onto Rust. But to be honest it has nothing to do with all the great stuff we’ve just explored. I came to Rust on a journey that began with WebAssembly. What started with me looking for a more efficient alternative to JavaScript for the web turned into figuring out just how powerful Rust turns out to be. From its seamless interop…

Automatically generate binding code between Rust, WebAssembly, and JavaScript APIs. Take advantage of libraries like web-sys that provide pre-packaged bindings for the entire web platform. – Rust website

To how fast and predictable its performance is. Everything in our lives evolves. Our smartphones, our cars, our home appliances, our own bodies. C++ while still incredibly powerful, fast and versatile can only take us so far. There is no harm in exploring alternatives, especially one as exceptional and with as much promise as Rust.

What do you guys think? Have you or would you give Rust a try? Let us know your thoughts in the comments section below.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

Why you should move from Node.js to Rust in 2019

Rust Vs. Haskell: Which Language is Best for API Design?

7 reasons why you should learn Rust programming language in 2019

An introduction to Web Development with Rust for Node.js Developers

Why you should learn the Rust programming language

Why you should learn the Rust programming language

Rust has many features that make it useful, but developers and their needs differ. I cover five of the key concepts that make Rust worth learning

Discover the history, key concepts, and tools for using Rust

A recent Stack Overflow survey found that almost 80% of respondents loved using or wanted to develop with the Rust language. That’s an incredible number! So, what’s so good about Rust? This article explores the high points of this C-like language and illustrates why it should be next on your list of languages to learn.

Rust and its genealogy

First, let’s start with a quick history lesson. Rust is a new language relative to its predecessors (most importantly C, which preceded it by 38 years), but its genealogy creates its multiparadigm approach. Rust is considered a C-like language, but the other features it includes create advantages over its predecessors (see Figure 1).

First, Rust is heavily influenced by Cyclone (a safe dialect of C and an imperative language), with some aspects of object-oriented features from C++. But, it also includes functional features from languages like Haskell and OCaml. The result is a C-like language that supports multiparadigm programming (imperative, functional, and object oriented).

Figure 1. Rust and its family tree

Key concepts in Rust

Rust has many features that make it useful, but developers and their needs differ. I cover five of the key concepts that make Rust worth learning and show these ideas in Rust source.

First, to get a feel for the code, let’s look at the canonical “Hello World” program that simply emits that message to the user (see Listing 1).

Listing 1. “Hello World” in Rust
fn main()
{
   println!( "Hello World.");
}

This simple program, similar to C, defines a main function that is the designated entry point for the program (and every program has one). The function is defined with the fnkeyword followed by an optional set of parameters within parentheses (()). The curly braces ({}) delineate the function; this function consists of a call to the println! macro, which emits formatted text to the console (stdout), as defined by the string parameter.

Rust includes a variety of features that make it interesting and worth the investment to learn. You’ll find concepts like modules for reusability, memory safety and guarantees (safe vs. unsafe operations), unrecoverable and recoverable error handling features, support for concurrency, and complex data types (called collections).

Reusable code via modules

Rust allows you to organize code in a way that fosters its reuse. You achieve this organization by using modules, which contain functions, structures, and even other modules that you can make public (that is, expose to users of the module) or private (that is, use only within the module and not by the module users — at least not directly). The module organizes code as a package that others can use.

You use three keywords to create modules, use modules, and modify the visibility of elements in modules.

  • The mod keyword creates a new module
  • The use keyword lets you use the module (expose the definitions into scope to use them)
  • The pub keyword makes elements of the module public (otherwise, they’re private).

Listing 2 provides a simple example. It starts by creating a new module called bits that contains three functions. The first function, called pos, is a private function that takes a u32 argument and returns a u32 (as indicated by the -> arrow), which is a 1 value shifted left bit times. Note that a return keyword isn’t needed here. This value is called by two public functions (note the pub keyword): decimal and hex. These functions call the private pos function and print the value of the bit position in decimal or hexadecimal format (note the use of  to indicate hexadecimal format). Finally, it declares a main function that calls the bits module’s two public functions, with the output shown at the end of Listing 2 as comments.

Listing 2. Simple module example in Rust
mod bits {
fn pos(bit: u32) ‑> u32 {
1 << bit
}

pub fn decimal(bit: u32) {
println!("Bits decimal {}", pos(bit));
}

pub fn hex(bit: u32) {
println!("Bits decimal 0x{:x}", pos(bit));
}
}

Modules enable you to collect functionality in public or private ways, but you can also associate methods to objects by using the impl keyword.

Safety checks for cleaner code

The Rust compiler enforces memory safety guarantees and other checking that make the programming language safe (unlike C, which can be unsafe). So, in Rust, you’ll never have to worry about dangling pointers or using an object after it has been freed. These things are part of the core Rust language. But, in fields such as embedded development, it’s important to do things like place a structure at an address that represents a set of hardware registers.

Rust includes an unsafe keyword with which you can disable checks that would typically result in a compilation error. As shown in Listing 3, the unsafe keyword enables you to declare an unsafe block. In this example, I declare an unmutable variable x, and then a pointer to that variable called raw. Then, to de-reference raw (which in this case would print 1 to the console), I use the unsafe keyword to permit this operation, which would otherwise be flagged at compilation.

Listing 3. Unsafe operations in Rust
fn main() {
let a = 1;
let rawp = &a as const i32;

unsafe {
println!("rawp is {}", rawp);
}
}

You can apply the unsafe keyword to functions as well as blocks of code within a Rust function. The keyword is most common in writing bindings to non-Rust functions. This feature makes Rust useful for things like operating system development or embedded (bare-metal) programming.

Better error handling

Errors happen, regardless of the programming language you use. In Rust, errors fall into two camps: unrecoverable errors (the bad kind) and recoverable errors (the not-so-bad kind).

Unrecoverable errors

The Rust panic! function is similar to C‘s assert macro. It generates output to help the user debug a problem (as well as stopping execution before more catastrophic events occur). The panic! function is shown in Listing 4, with its executable output in comments.

Listing 04. Handling unrecoverable errors in Rust with panic!

fn main() {
panic!("Bad things happening.");
}

// thread 'main' panicked at 'Bad things happening.', panic.rs:2:4
// note: Run with RUST_BACKTRACE=1 for a backtrace.

From the output, you can see that the Rust runtime indicates exactly where the issue occurred (line 2) and emitted the provided message (which could emit more descriptive information). As indicated in the output message, you could generate a stack backtrace by running with a special environment variable called RUST_BACKTRACE. You can also invoke panic! internally based on detectable errors (such as accessing an invalid index of a vector).

Recoverable errors

Handling recoverable errors is a standard part of programming, and Rust includes a nice feature for error checking (see Listing 5). Take a look at this feature in the context of a file operation. The File::open function returns a type of Result<T, E>, where T and Erepresent generic type parameters (in this context, they represent std::fs::File and std::io::Error). So, when you call File::open and no error has occurred (E is Ok), T would represent the return type (std::fs::File). If an error occurred, E would represent the type of error that occurred (using the type std::io::Error). (Note that my file variable fuses an underscore [] to omit the unused variable warning that the compiler generated.)

I then use a special feature in Rust called match, which is similar to the switch statement in C but more powerful. In this context, I match _f against the possible error values (Okand Err). For Ok, I return the file for assignment; for Err, I use panic!.

Listing 5. Handling recoverable errors in Rust with Result<t, e>

use std::fs::File;

fn main() {
let _f = File::open("file.txt");

let _f = match _f {
Ok(file) => file,
Err(why) => panic!("Error opening the file {:?}", why),
};
}

// thread 'main' panicked at 'Error opening the file Error { repr: Os
// { code: 2, message: "No such file or directory" } }', recover.rs:8:23
// note: Run with RUST_BACKTRACE=1 for a backtrace.

Recoverable errors are simplified within Rust when you use the Result enum; they’re further simplified through the use of match. Note also in this example the lack of a File::close operation: The file is automatically closed when the scope of _f ends.

Support for concurrency and threads

Concurrency commonly comes with issues (data races and deadlocks, to name two). Rust provides the means to spawn threads by using the native operating system but also attempts to mitigate the negative effects of threading. Rust includes message passing to allow threads to communicate with one another (via send and recv as well as locking through mutexes). Rust also provides the ability to permit a thread to borrow a value, which gives it ownership and effectively transitions the scope of the value (and its ownership) to a new thread. Thus, Rust provides memory safety along with concurrency without data races.

Consider a simple example of threading within Rust that introduces some new elements (vector operations) and brings back some previously discussed concepts (pattern matching). In Listing 6, I begin by importing the thread and Duration namespaces into my program. I then declare a new function called my_thread, which represents the thread that I’ll create later. In this thread, I simply emit the thread’s identifier, and then sleep for a short time to permit the scheduler to allow another thread to run.

My main function is the heart of this example. I begin by creating an empty mutable vector that I can use to store values of the same type. I then create 10 threads by using the spawn function and push the resulting join handle into the vector (more on this later). This spawn example is detached from the current thread, which allows the thread to live after the parent thread has exited. After emitting a short message from the parent thread, I finally iterate the vector of JoinHandle types and wait for each child thread to exit. For each JoinHandle in the vector, I call the join function, which waits for that thread to exit before continuing. If the join function returns an error, I’ll expose that error through the match call.

Listing 6. Threads in Rust

use std::thread;
use std::time::Duration;

fn mythread() {
println!("Thread {:?} is running", std::thread::current().id());
thread::sleep(Duration::from_millis(1));
}

fn main() {
let mut v = vec![];

for _i in 1..10 {
v.push( thread::spawn(|| { my_thread(); } ) );
}

println!("main() waiting.");

for child in v {
match child.join() {
Ok() => (),
Err(why) => println!("Join failure {:?}", why),
};
}
}

On execution, I see the output provided in Listing 7. Note here that the main thread continued to execute until the join process had begun. The threads then executed and exited at different times, identifying the asynchronous nature of threads.

Listing 7. Thread output from the example code in Listing 6

main() waiting.
Thread ThreadId(7) is running
Thread ThreadId(9) is running
Thread ThreadId(8) is running
Thread ThreadId(6) is running
Thread ThreadId(5) is running
Thread ThreadId(4) is running
Thread ThreadId(3) is running
Thread ThreadId(2) is running
Thread ThreadId(1) is running

Support for complex data types (collections)

The Rust standard library includes several popular and useful data structures that you can use in your development, including four types of data structures: sequences, maps, sets, and a miscellaneous type.

For sequences, you can use the vector type (Vec), which I used in the threading example. This type provides a dynamically resizeable array and is useful for collecting data for later processing. The VecDeque structure is similar to Vec, but you can insert it at both ends of the sequence. The LinkedList structure is similar to Vec, as well, but with it, you can split and append lists.

For maps, you have the HashMap and BTreeMap structures. You use the HashMap structure to create key-value pairs, and you can reference elements by their key (to retrieve the value). The BTreeMap is similar to the HashMap, but it can sort the keys, and you can easily iterate all the entries.

For sets, you have the HashSet and BTreeSet structures (which you’ll note follow the maps structures). These structures are useful when you don’t have values (just keys) and you easily recall the keys that have been inserted.

Finally, the miscellaneous structure is currently the BinaryHeap. This structure implements a priority queue with a binary heap.

Installing Rust and its tools

One of the simplest ways to install Rust is by using curl through the installation script. Simply execute the following string from the Linux® command line:

curl -sSf https://static.rust-lang.org/rustup.sh | sh

This string transfers the rustup shell script from rust-lang.org, and then passes the script to the shell for execution. When complete, you can execute rustc -v to show the version of Rust you installed. With Rust installed, you can maintain it by using the rustuputility, which you can also use to update your Rust installation.

The Rust compiler is called rustc. In the examples shown here, the build process is simply defined as:

rustc threads.rs

…where the rust compiler produces a native executable file called threads. You can symbolically debug Rust programs by using either rust-lldb or rust-gdb.

You’ve probably noticed that the Rust programs I’ve demonstrated here have a unique style. You can learn this style through the automatic Rust source formatting by using the rustfmt utility. This utility, executed with a source file name, will automatically format your source in a consistent, standardized style.

Finally, although Rust is quite strict in what it accepts for source, you can use the rust-clippy program to dive further in to your source to identify elements of bad practice. Think of rust-clippy as the C lint utility.

Windows considerations

On Windows, Rust additionally requires the C++ build tools for Visual Studio 2013 or later. The easiest way to acquire the build tools is by installing Microsoft Visual C++ Build Tools 2017 which provides just the Visual C++ build tools. Alternately, you can install Visual Studio 2017, Visual Studio 2015, or Visual Studio 2013 and during the install, select C++ tools.

For further information about configuring Rust on Windows, see the Windows-specific rustup documentation.

Going further

In mid-February 2018, the Rust team released version 1.24. This version includes incremental compilation, automatic source formatting with rustfmt, new optimizations, and library stabilizations. You can learn more about Rust and its evolution at the Rust blog and download Rust from the Rust Language website. There, you can read about the many other features Rust offers, including pattern matching, iterators, closures, and smart pointers.

Originally published on https://developer.ibm.com

Introduces Pass-By-Value in C++ and Rust

 Introduces Pass-By-Value in C++ and Rust

C++ and Rust are often compared to each other. They occupy a similar space in terms of power and flexibility - neither has a garbage collector and thus can fit in resource-constrained domains, yet both provide richer high-level tools than a language like C which increase safety and correctness.

However, the experience of writing a program in each can be pretty different. Once such difference beginners in Rust will run into quickly is what happens when you pass a parameter by value. Rust handles this situation differently than C++, and it's worth exploring why.

C++

In C++, passing by value passes a copy of the object into the function. This is fine for primitives like integers - my 5 is the same as your 5. The fact that they're distinct values in memory won't ever matter for their use, because the meaning of 5 isn't context or state dependent. Lots of other things are, though. When an object is copied in C++, its copy constructor gets called. These have a prototype that looks like this:

classname (const classname &obj);

When an object is passed as a parameter to a method, this constructor is used to copy the object into the function body. Check out that keyword at the beginning of the parameter list, "const". This means we can't use this constructor to make any changes to the initial object. Instead, it's just going to create a new copy, which is what's getting used inside any function. To illustrate, here's a simple class with just a single data member, a default constructor, and a getter and setter:

class CoolObject
{
    int coolValue;

public:
    CoolObject()
    {
        coolValue = 5;
    }
    int getCoolValue() const
    {
        return coolValue;
    }
    void setCoolValue(int val)
    {
        coolValue = val;
    }
};

We'll write a function that takes one of these objects by value and sets it to 10:

#include <iostream>

void setCoolValueToTen(CoolObject co)
{
    using std::cout;
    cout << "Current: " << co.getCoolValue() << " | Setting...\n";
    co.setCoolValue(10);
    cout << "New: " << co.getCoolValue() << "\n";
};

If we make two of these, and use this function on one, you'd expect it to stick, right?

int main()
{
    using std::cout;
    CoolObject co1;
    CoolObject co2;
    cout << "co1: " << co1.getCoolValue() << " | co2: " << co2.getCoolValue() << "\n";
    setCoolValueToTen(co2);
    cout << "co1: " << co1.getCoolValue() << " | co2: " << co2.getCoolValue();
    return 0;
}

Instead, we get the following:

co1: 5 | co2: 5
Current: 5 | Setting...
New: 10
co1: 5 | co2: 5

The code inside the setCoolValueToTen() function is operating on its very own copy, made from and identical to co2 when it was passed in but entirely distinct from it. Calling the setter on this local instance has no effect on co2, because it's no longer involved.

If you pass by value, all your changes are stuck in this new local copy and never make it back to your intended target. A reference to the original solves this problem:

void reallySetCoolValueToTen(CoolObject &co) // Just take a reference - rest is identical!
{
    using std::cout;
    cout << "Current: " << co.getCoolValue() << " | Setting...\n";
    co.setCoolValue(10);
    cout << "New: " << co.getCoolValue() << "\n";
}

int main()
{
    using std::cout;
    CoolObject co1;
    CoolObject co2;
    cout << "co1: " << co1.getCoolValue() << " | co2: " << co2.getCoolValue() << "\n";
    setCoolValueToTen(co2);
    cout << "co1: " << co1.getCoolValue() << " | co2: " << co2.getCoolValue() << "\n";
    reallySetCoolValueToTen(co2);
    cout << "co1: " << co1.getCoolValue() << " | co2: " << co2.getCoolValue() << "\n";
    return 0;
}

The second call works as expected:

co1: 5 | co2: 5
Current: 5 | Setting...
New: 10
co1: 5 | co2: 5
Current: 5 | Setting...
New: 10
co1: 5 | co2: 10

Rust

Let's attempt to re-implement this small program in Rust. Here's our CoolObject:

struct CoolObject {
    cool_value: i32,
}

impl CoolObject {
    fn get_cool_value(&self) -> i32 {
        self.cool_value
    }
    fn set_cool_value(&mut self, val: i32) {
        self.cool_value = val;
    }
}

impl Default for CoolObject {
    fn default() -> Self {
        Self { cool_value: 5 }
    }
}

We need a function to set the value to ten, taking the parameter by value:

fn set_cool_value_to_ten(mut co: CoolObject) {
    println!("Current: {} | Setting...", co.get_cool_value());
    co.set_cool_value(10);
    println!("New: {}", co.get_cool_value());
}

We're already starting to see a problem - we can't just mutate values without asking first, like we can in C++. If I hadn't included that mut in the parameter list, the set_cool_value() call would complain: "cannot borrow co as mutable, as it is not declared as mutable". We need to specifically tell the compiler that we intend to mutate the object.

Let's try to emulate the first go of the C++ version:

fn main() {
    let co1 = CoolObject::default();
    let co2 = CoolObject::default();
    println!("co1: {} | co2: {}", co1.get_cool_value(), co2.get_cool_value());
    set_cool_value_to_ten(co2);
    println!("co1: {} | co2: {}", co1.get_cool_value(), co2.get_cool_value());
}

Attempting to compile this code will net you an error like the following:

error[E0382]: borrow of moved value: `co2`
  --> src/main.rs:34:57
   |
31 |     let co2 = CoolObject::new();
   |         --- move occurs because `co2` has type `CoolObject`, which does not implement the `Copy` trait
32 |     println!("co1: {} | co2: {}", co1.get_cool_value(), co2.get_cool_value());
33 |     set_cool_value_to_ten(co2);
   |                           --- value moved here
34 |     println!("co1: {} | co2: {}", co1.get_cool_value(), co2.get_cool_value());
   |                                                         ^^^ value borrowed here after move

error: aborting due to previous error

And there's the problem. When pass by value in C++, the compiler will just assume you know what you're doing and call a copy constructor for you, even if it doesn't really make sense. If you haven't manually defined a copy constructor, no sweat - the compiler will do it's damndest to generate one for you and call that. After all, you've passed by value, s this must be what you want!

Rust pumps the brakes. When you pass by value, it actually moves ownership of the original value. It's not copying the original object in, it's actually bringing the object from outside - but the caveat is that the calling scope no longer owns this value at all, the new function does. When set_cool_value_to_ten() reaches the end of its body, this value goes out of scope! It's dropped. When we attempt to refer to co2 again in the next line, we can't - it's not ours to use anymore.

In Rust, any value only has one owner. You can borrow as many immutable references as you like, which we do when we call get*cool*value(&self), or we can have one single mutable reference, like with really_set_cool_value_to_ten(co: &mut CoolObject), but if there's no borrow, like with set_cool_value_to_ten(mut co: CoolObject), you know ownership of this value will be moving.

_____________________________________________________________________________

You might also enjoy: What's unique about Rust?_____________________________________________________________________________

This skirts the common pass-by-value bug in C++ where you think you're working with an object but you're actually just working with a copy. C++ will just silently try to make things work, and may not be on the same page as you are. Rust is very explicit. It even specifically tells you that if your object did implement the Copy trait, it would have attempted to copy the value - but of course, this still wouldn't solve this problem. As with C++, the solution is to refer to the original instead of move the value. In C++, you say "take a reference", but in Rust, you'd call it a "mutable borrow":

fn really_set_cool_value_to_ten(co: &mut CoolObject) {
    println!("Current: {} | Setting...", co.get_cool_value());
    co.set_cool_value(10);
    println!("New: {}", co.get_cool_value());
}

We also need to declare co2 itself as mutable:

fn main() {
    let co1 = CoolObject::new();
    let mut co2 = CoolObject::new(); // right here
    println!("co1: {} | co2: {}", co1.get_cool_value(), co2.get_cool_value());
    really_set_cool_value_to_ten(&mut co2); // and pass a mutable reference
    println!("co1: {} | co2: {}", co1.get_cool_value(), co2.get_cool_value());
}

This illustrates one of the reasons I prefer working with Rust over C++. In C++, the programmer just has to know all of these details about how the language operates, and the compiler has no qualms about implicit actions that it takes. You've got no help reading through your code to figure out where you've made this mistake, and even full awareness of this issue is insufficient to avoid it in 100% of cases. Rust, on the other hand, doesn't let you ask for stupid things. In this situation, the compiler was able to tell me in plain English why my code was incorrect and how to fix it.

Further Reading

Building a Retro Computer in Embedded Rust Language

What is Tokio-Trace Rust Library?

What and How of Futures and async/await in Rust

Originally published by Ben Lovy at*** ***dev.to

========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter