log

taiar

Rust Programming Language - Usage

This is the eighth post of a serie where I’ll talk about some new programming languages with a nice future ahead. During this series I’ll explore deeper the Ceylon, Dart, Elixir, Rust and Swift 2 languages.

The plan is to make a post a week until December 15 (or maybe a little more) and spend 2 weeks exploring each one of them. In the first week I’ll explore the general aspects of the language and make some comparisons with other very known and established languages. In the post of the second week I’ll go deeper inside each one of the languages and explore the individual advantages on use them.

All the posts

  1. Ceylon Introduction
  2. Ceylon Usage
  3. Dart Introduction
  4. Dart Usage
  5. Elixir Introduction
  6. Elixir Usage
  7. Rust Introduction
  8. Rust Usage

Preface

There are hundred of programming languages out there. Which one should we use? Which help do we have to choose well? How do they compare to each other? This document is an attempt to provide some answers to these questions. Naturally, it would not be possible to provide complete answers: as I mentioned, there are too many programming languages. Nevertheless, we chose five languages with a potential to grow in importance in the coming years. These programming languages are Elixir, Rust, Dart, Swift and Ceylon. During this project, we shall be talking about each one of them. These discussions will be in breath, not in depth. Their goal is to provide the reader with the minimum of information necessary to compare them, and who knows, to lure one or other interested person in learning them in a greater level of details. In any case, we hope to contribute a bit to the popularization of these programming languages, which - likely - will be paramount to the development of computer science in the next ten years.

Rust Example

Rust was made to be a system’s programming language. There are lots of ways to integrate Rust programs into computer systems and other programming languages. In this usage example we’ll see how to write a system shared library in Rust and use it from other programs in other languages.

Our problem

This is the Monte Carlo algorithm for a Pi approximation

1 written in Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
import random
import sys

def montecarlopi(n):
    m = 0.
    n = int(n)
    for i in range(0, n):
        x, y = random.random(), random.random()
        if ((x ** 2) + (y ** 2)) < 1:
            m = m + 1
    return 4 * m / n

print montecarlopi(sys.argv[1])

It computes a approximation for the Pi value, based in the given parameter. When bigger is the parameter, better is the approximation value. Let’s measure some executions:

1
2
3
4
5
6
$ time python pi.py 10
3.6

real    0m0.016s
user    0m0.015s
sys 0m0.000s

The programs is fast and the value is a very bad approximation. Let’s increase the precision:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ time python pi.py 100000
3.14184

real    0m0.067s
user    0m0.058s
sys 0m0.008s

$ time python pi.py 1000000
3.140332

real    0m0.533s
user    0m0.504s
sys 0m0.028s

$ time python pi.py 10000000
3.1423776

real    0m5.192s
user    0m4.912s
sys 0m0.276s

$ time python pi.py 100000000
3.14162228

real    3m55.038s
user    0m52.011s
sys 0m6.175s

The program became very slow very fast. It’s a common approach to implement the slow algorithms of the systems in platforms that runs fast and integrate into the real workflow by calling this solution in other platform. So, let’s take a look on the implementation of this algorithm in Rust:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
extern crate rand;

use std::env;
use std::str::FromStr;
use rand::distributions::{IndependentSample, Range};

fn montecarlopi(n: u32) -> f32 {
    let between = Range::new(-1f32, 1.);
    let mut rng = rand::thread_rng();

    let total = n;
    let mut in_circle = 0;

    for _ in 0..total {
        let a = between.ind_sample(&mut rng);
        let b = between.ind_sample(&mut rng);
        if a*a + b*b <= 1. {
            in_circle += 1;
        }
    }
    4. * (in_circle as f32) / (total as f32)
}

fn main() {
   let args: Vec<String> = env::args().collect();
   if args.len() > 1 {
     println!("{}", montecarlopi(FromStr::from_str(&args[1]).unwrap()));
   }
}

It Is the same algorithm at all. Let’s measure the execution of this program:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ time ./release/point 100000000
3.1413338

real    0m5.323s
user    0m1.911s
sys 0m3.410s

time ./release/point 1000000000
3.1415544

real    0m53.193s
user    0m19.046s
sys 0m34.107s

Awesome! The execution time is much better (even with more precision). Now we should make it callable from Python and other platforms.

A Rust library

Let’s start creating a new project with Cargo:

1
2
$ cargo new montepy
$ cd montepy

Now, edit the ./src/lib.rs file Cargo creates inserting our previous code with some changes I’ll explain later:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extern crate rand;

use std::env;
use std::str::FromStr;
use rand::distributions::{IndependentSample, Range};

#[no_mangle]
pub extern fn montecarlopi(n: u32) -> f32 {
    let between = Range::new(-1f32, 1.);
    let mut rng = rand::thread_rng();

    let total = n;
    let mut in_circle = 0;

    for _ in 0..total {
        let a = between.ind_sample(&mut rng);
        let b = between.ind_sample(&mut rng);
        if a*a + b*b <= 1. {
            in_circle += 1;
        }
    }
    4. * (in_circle as f32) / (total as f32)
}

The not so odd modification is the pub extern annotations in the font of the montecarlopi function. The pub means that this function should be callable from outside of this module, and the extern says that it should be able to be called from C. Also the main method was removed because we don’t want this program to execute itself. The other modification was the # [no_mangle] annotation over the method. When you create a Rust library, it changes the name of the function in the compiled output. This attribute turns that behavior off 2.

Also we had to change the Cargo.toml file. It will look like this:

1
2
3
4
5
6
7
8
9
10
11
[package]
name = "montepy"
version = "0.1.0"
authors = ["André Taiar <taiar@dcc.ufmg.br>"]

[dependencies]
rand = "*"

[lib]
name = "montepi"
crate-type = ["dylib"]

The additions here where the dependencies clause, specifying that rand library is our dependence (Cargo will download and install it for us), the lib clause, specifying we are building a library and we want to compile our library into a standard dynamic library 3. Let’s build the library:

1
2
3
4
5
6
7
8
$ cargo build --release
    Updating registry `https://github.com/rust-lang/crates.io-index`
   Compiling winapi v0.2.5
   Compiling libc v0.2.2
   Compiling winapi-build v0.1.1
   Compiling advapi32-sys v0.1.2
   Compiling rand v0.3.12
   Compiling montepy v0.1.0 (file:///home/taiar/dev/mono1/rust/montepy)

Now in the ./target/release/ directory there is a file called libmontepi.so which is our compiled dynamic library. Now, we can create a python program that uses this library and compute our result very fast. Here is the code:

1
2
3
4
5
6
from ctypes import cdll

lib = cdll.LoadLibrary("target/release/libmontepi.so")
print lib.montecarlopi(100000000)

print("done!")

The execution result:

1
2
3
4
5
6
7
$ time python pi.py
22862944
done!

real    0m5.412s
user    0m2.061s
sys 0m3.343s

The program runs really fast but wait, there is a problem. The aproximation result is totally wrong.

The problem is that the integers and floats we use in our implementation of the Rust algorithm has different forms from the types of Python. To get around this, we must convert our numbers for types that are compatible with our system. Here is our new Python code:

1
2
3
4
5
6
7
8
9
10
from ctypes import cdll
import ctypes

lib = cdll.LoadLibrary("target/release/libmontepi.so")

montecarlopi = lib.montecarlopi
montecarlopi.restype = ctypes.c_float
print montecarlopi(100000000)

print("done!")
1
2
3
4
5
6
7
$ time python pi.py
3.14137887955
done!

real  0m5.400s
user  0m1.952s
sys 0m3.447s

and in Node.js (with the ffi package 4):

1
2
3
4
5
6
7
var ffi = require('ffi');

var pipi = ffi.Library('./target/release/libmontepi.so', {
    'montecarlopi': ['float', ['int']]
});

console.log(pipi.montecarlopi(100000000));
1
2
3
4
5
6
$ time nodejs pi.js
3.1415293216705322

real  0m5.466s
user  0m2.038s
sys 0m3.420s

and in C (the libmontepi.so file must be available in your system):

1
2
3
4
5
6
7
8
9
#include <stdint.h>
#include <stdio.h>

float montecarlopi(const int precision);

int main() {
  printf("%f\n", montecarlopi(100000000));
  return 0;
}
1
2
3
4
5
6
7
$ gcc -o pi pi.c -lmontepi
$ time ./pi
3.141783

real  0m5.367s
user  0m1.925s
sys 0m3.441s

References