All the C++ and Rust examples in the article are not real. So use your imagination to estimate the scope of threat.
C++ programs have been compiled with gcc-4.7.2 in C++11 mode, using online compiler. Programs in Rust have been built with Rust 0.11, using the rust play app.
I know that there are a lot of changes in C++14 as well as in latest Rust release. But I would still be glad to read your feedback. You’re also welcome to share any information about D.
Template Types
C++ author has not been happy as for templates implementation in the language. He called them «compile-time duck typing» in one of his Lang-NEXT talks.
The thing is that it’s not always clear, what we should use to instantiate a template looking at its declaration. The situation is worsened by monster-like error messages. Let’s try to build the following program:
#include
#include
int main()
{
int a;
std::vector > v;
std::vector >::const_iterator it = std::find( v.begin(), v.end(), a );
}
Just imagine the happiness of a person reading the longest error message.
As for the Rust, templates and their correctness are verified before instantiation. Therefore, there’s a sharp distinction between errors in the template itself (there should be no errors if you’re using a third-party template) and in the place of instantiation. All you should do there is meet requirements to the type that are described in the template:
trait Sortable {}
fn sort(array: &mut [T]) {}
fn main() {
sort(&mut [1,2,3]);
}
This code does not compile due to an obvious reason:
demo:5:5: 5:9 error: failed to find an implementation of trait Sortable for int
demo:5 sort(&mut [1,2,3]);
Reclaimed Memory
There’s a series of problems in C++ that are represented by an undefined behavior, which occurs if you try to use the already freed memory.
An example:
int main() {
int *x = new int(1);
delete x;
*x = 0;
}
Since there are no commands to deallocate memory, such problems are impossible in Rust. Memory on the stack is «alive» as long as it’s in the scope. Rust compiler automatically frees the memory for you when a pointer goes out of scope. If the memory has been allocated in a heap, a pointer to it (Box
A lost pointer to the Local Variable
С++ variant:
#include
int *bar(int *p) {
return p;
}
int* foo(int n) {
return bar(&n);
}
int main() {
int *p1 = foo(1);
int *p2 = foo(2);
printf("%d, %d\n", *p1, *p2);
}
At the output:
2, 2
Rust variant:
fn bar(p: &'a int) -> &'a int {
return p;
}
fn foo(n: int) -> &int {
bar(&n)
}
fn main() {
let p1 = foo(1);
let p2 = foo(2);
println!("{}, {}", *p1, *p2);
}
The compiler returns the following:
demo:5:10: 5:11 error: `n` does not live long enough
demo:5 bar(&n)
^
demo:4:24: 6:2 note: reference must be valid for the anonymous lifetime #1 defined on the block at 4:23…
demo:4 fn foo(n: int) -> &int {
demo:5 bar(&n)
demo:6 }
demo:4:24: 6:2 note: ...but borrowed value is only valid for the block at 4:23
demo:4 fn foo(n: int) -> &int {
demo:5 bar(&n)
demo:6 }
Uninitialized Variables
#include
int minval(int *A, int n) {
int currmin;
for (int i=0; i
Prints 0. The result is actually ambiguous, though. Here’s the same in Rust:
fn minval(A: &[int]) -> int {
let mut currmin;
for a in A.iter() {
if *a
The code does not compile and returns the following error:
use of possibly uninitialized variable: `currmin`
More idiomatic (and operable) variant of this function would look like the following:
fn minval(A: &[int]) -> int {
A.iter().fold(A[0], |u,&a| {
if a
Implicit Copy Constructor
struct A{
int *x;
A(int v): x(new int(v)) {}
~A() {delete x;}
};
int main() {
A a(1), b=a;
}
The code is built, but crashes during the runtime:
*** glibc detected *** demo: double free or corruption (fasttop): 0x0000000000601010 ***
The same in Rust:
struct A{
x: Box
}
impl A {
pub fn new(v: int) -> A {
A{ x: box v }
}
}
impl Drop for A {
fn drop(&mut self) {} //there’s no point in it. It’s provided for the exact copy of C++ code
}
fn main() {
let a = A::new(1);
let _b = a;
}
The code builds and runs with no error. There’s no copying as the object does not implement trait Copy.
Rust won’t do anything behind your back. Do you want an automatic implementation of Eq or Clone? Simply add deriving to your structure:
#[deriving(Clone, Eq, Hash, PartialEq, PartialOrd, Ord, Show)]
struct A{
x: Box
}
Memory Overlap
#include
struct X { int a, b; };
void swap_from(X& x, const X& y) {
x.a = y.b; x.b = y.a;
}
int main() {
X x = {1,2};
swap_from(x,x);
printf("%d,%d\n", x.a, x.b);
}
Output:
2,2
The function doesn’t explicitly expect to receive references to the same object. There’s restrict in C99 to persuade the compiler that references are unique. It serves as a hint for the optimizer and does not guarantee that there will be no overlaps: the program will be compiled and executed as before.
Let’s try to do the same in Rust:
struct X { pub a: int, pub b: int }
fn swap_from(x: &mut X, y: &X) {
x.a = y.b; x.b = y.a;
}
fn main() {
let mut x = X{a:1, b:2};
swap_from(&mut x, &x);
}
Returns the following:
demo:7:24: 7:25 error: cannot borrow `x` as immutable because it is also borrowed as mutable
demo:7 swap_from(&mut x, &x);
^
demo:7:20: 7:21 note: previous borrow of `x` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x` until the borrow ends
demo:7 swap_from(&mut x, &x);
^
demo:7:26: 7:26 note: previous borrow ends here
demo:7 swap_from(&mut x, &x);
As you can see, the compiler does not allow to reference to the same variable via "&mut" and "&" at once. Therefore, it guarantees that no one will be able to read or modify the mutable variable as long as &mut reference is active. These guarantees are calculated during the building process and do not slow down the execution of the program itself. Moreover, this code compiles as is we used restrict pointers in C99 (Rust provides LLVM information as for references uniqueness). It gives the optimizer a green light.
A Broken Iterator
#include
int main() {
std::vector v;
v.push_back(1);
v.push_back(2);
for(std::vector::const_iterator it=v.begin(); it!=v.end(); ++it) {
if (*it
The code is built without errors, but crashes during the runtime.
Segmentation fault (core dumped)
Let’s try to convert it to Rust:
fn main() {
let mut v: Vec = Vec::new();
v.push(1);
v.push(2);
for x in v.iter() {
if *x
The compiler doesn’t allow to run it by pointing out that we can’t change the vector whilst traversing it
demo:7:13: 7:14 error: cannot borrow `v` as mutable because it is also borrowed as immutable
demo:7 v.push(5-*x);
^
demo:5:14: 5:15 note: previous borrow of `v` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `v` until the borrow ends
demo:5 for x in v.iter() {
^
demo:10:2: 10:2 note: previous borrow ends here
demo:5 for x in v.iter() {
demo:6 if *x
A Dangerous Switch
#include
enum {RED, BLUE, GRAY, UNKNOWN} color = GRAY;
int main() {
int x;
switch(color) {
case GRAY: x=1;
case RED:
case BLUE: x=2;
}
printf("%d", x);
}
Returns 2. In Rust you’re obliged to enumerate all variants when pattern matching. Besides, the code doesn’t automatically jump to the next case, unless it meets break. A proper reaction in Rust will look like the following:
enum Color {RED, BLUE, GRAY, UNKNOWN}
fn main() {
let color = GRAY;
let x = match color {
GRAY => 1,
RED | BLUE => 2,
_ => 3,
};
println!("{}", x);
}
An Odd Semicolon
int main() {
int pixels = 1;
for (int j=0; j
In Rust, bodies of loop structures must be wrapped in braces. It's a simple thing, but helps to reduce a set of typos.
Multithreading
#include
#include
#include
class Resource {
int *value;
public:
Resource(): value(NULL) {}
~Resource() {delete value;}
int *acquire() {
if (!value) {
value = new int(0);
}
return value;
}
};
void* function(void *param) {
int *value = ((Resource*)param)->acquire();
printf("resource: %p\n", (void*)value);
return value;
}
int main() {
Resource res;
for (int i=0; i
Spawns multiple resources instead of just one:
done
resource: 0x7f229c0008c0
resource: 0x7f22840008c0
resource: 0x7f228c0008c0
resource: 0x7f22940008c0
resource: 0x7f227c0008c0
It is a common problem of thread synchronization. It occurs when the object is changed concurrently by several threads. Let’s try to write the same in Rust:
struct Resource {
value: Option,
}
impl Resource {
pub fn new() -> Resource {
Resource{ value: None }
}
pub fn acquire(&'a mut self) -> &'a int {
if self.value.is_none() {
self.value = Some(1);
}
self.value.get_ref()
}
}
fn main() {
let mut res = Resource::new();
for _ in range(0,5) {
spawn(proc() {
let ptr = res.acquire();
println!("resource {}", ptr)
})
}
}
Since we can’t modify the object that is the same for all threads, it throws an error.
demo:20:23: 20:26 error: cannot borrow immutable captured outer variable in a proc `res` as mutable
demo:20 let ptr = res.acquire();
That’s how the fixed code meeting the compiler requirements can look like:
extern crate sync;
use sync::{Arc, RWLock};
struct Resource {
value: Option>,
}
impl Resource {
pub fn new() -> Resource {
Resource{ value: None }
}
pub fn acquire(&mut self) -> *int {
if self.value.is_none() {
self.value = Some(box 1)
}
&**self.value.get_ref() as *int
}
}
fn main() {
let arc_res = Arc::new(RWLock::new(Resource::new()));
for _ in range(0,5) {
let child_res = arc_res.clone();
spawn(proc() {
let ptr = child_res.write().acquire();
println!("resource: {}", ptr)
})
}
}
It uses Arc (Atomically Reference Counted) and RWLock (to lock shared modifications) the synchronization primitives. We get the following at the output:
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378
resource: 0x7ff4b0010378
Of course, we can write it properly in C++, as well as in the assembler. Rust doesn’t let you shoot in the foot by protecting you from your own mistakes. As a rule, if the program is built, it operates. It’s better to spend half an hour for adjusting the code to the form that is acceptable by the compiler. Otherwise you’ll have to fix synchronization errors for months (the cost of bugs fixing).
The Unsafe Code
Rust allows playing with raw pointers as much as you like, but not inside unsafe{} block. It’s the case when you ask your compiler not to interfere as you know what you’re doing. For instance, all “foreign” functions (from the library written in C you’re merging with) are automatically marked as unsafe. The language philosophy lies in isolating small parts of the unsafe code from the main part (of the normal code) utilizing secured interfaces. For example, you can find unsafe parts in implementations of Cell and Mutex classes. Isolating the unsafe code allows not only to narrow the search of the occurred problem, but to cover it with tests (we’re on friendly terms with TDD!).
4 comments
Coming from nodejs background, what you say is entirely replicated to a problem where you block the main event loop and state javascript is bs language.
«Uninitialized Variables» — this one you can surely detect using -Wall compiler flag.
«A lost pointer to the Local Variable» — well, surprise, C++ is quite low level language, so you have to actually think and know what you are doing.
«Reclaimed Memory» — you are comparing pointers and references, it seems to me. Compare at least smart pointers with Rust… You can use GC in C++ too, using the right libraries…
Upload image