W.B Storytime

Returning an array from a function in C and Rust

There are subtle things that make the developer experience much better when working on "lower" level languages. One of the main complaints I still have against C to this day is the lacking ability to be able to return an array from a function. Though one is able to return a struct from a function as seen below.

Where as with Rust, its as easy as:



The spectrum of typing

A couple days back I was talking to another dev at some well known tech company and an interesting exchange took place:

me: so languages with stronger types unlike Java
*interrupts*
other: Java has strong types.
me: Hmmm? It has static types, yep, but not strong types.
other: No it has strong types, it has primitives like an int
me: Well, if Java has strong types what does one call Ocaml or even Haskell's types? Super types
other: ...

This isn't a rare occurrence, its more the general rule. What is the general source of confusion behind this and why is it all so prevalent? Is it due to the lack of popularity behind the ML/functional languages? Is it that most college programs end up using C++ or Java as the language of choice for most courses? I know some are moving on to Python in order to keep up with the times... Perhaps it's because a language like Python with dynamic typing and so the opposite static type means strong?

But there is a spectrum to everything clearly.

Things possible in a stronger type system than run of the mill statically typed languages lack:

  1. Type inference: means you don't have to declare the type in use every time
  2. Algebraic data types
  3. Pattern matching ~ or better yet exhaustive pattern matching!
  4. Lightly create and define new types that are compile time constructs

Static types aren't all the same

When thinking about statically typed languages, what comes to mind? Probably a short list of C++, Java, C# or some combination of those right? Well lets observe the behavior in a trivial example for our farmer Joe:

int apples = 10;
int pears = 8;
/* now have a function that correctly sums up the total fruits */
int addFruits(int appleCount, int pearCount) {
    return appleCount + pearCount;
}
std::vector<int> appleBasket;
/* function that adds apples to the basket */
void addApple(int apple){
    return appleBasket.push_back(apple);
}


Easy enough, let's call it a day and go home... right?

Apples and pears aren't the same thing so how can we treat them as such? Taking a look at the addApple function one can easily just pass in a pear or any other integer. Clearly this isn't correct, nor is it something anyone should be okay with. The standard solution in OOP land would be to think "oh its easy let's just make a class and blah blah blah".

What if there was a better way? 
*hint: there is.


Linked Lists in Rust

I recently ran into needing to write a simple linked list in Rust and ran into some basic issues which are quirky for someone still getting used to the semantics and demanding compiler. For instance, a basic list implemented as such:
struct List<T> {
    value: T,
    next: List<T>
}
When running the command "cargo check" to verify if its valid will have this:
error[E0072]: recursive type `List` has infinite size
 --> src/lib.rs:4:1
  |
4 | struct List<T> {
  | ^^^^^^^^^^^^^^ recursive type has infinite size
5 |     value: T,
6 |     next: List<T>
  |     ------------- recursive without indirection
  |
  = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0072`.
error: Could not compile `linked_list`.

Interestingly, one runs into the idea of a "recursive type" which is annoying at first but stepping back for a second before trying to fight the compiler will lead one to the idea behind the error. As Rust is compiled and it needs to keep track of memory at compile time, it needs to be told by *you*  the developer what to allot and how to think of this declaration. What if you only need 8 bytes or what if you actually need 1 kb? How do you reason about such a thing and properly deal with the memory needed?

Well you don't actually, and so as most of the memory one deals with Rust ends up being stack allocated a different call potentially using Box needs to be done. Box essentially is a pointer to the heap and so it gives us the ability to use a yet to be defined amount of memory for our sweet recursive definition of our struct List.

So now changing the implementation to this, checks just fine:
struct List<T> {
    value: T,
    next: Option<Box<List<T>>>,
}

impl<T> List<T> {
    fn new(value: T, next: Option<Box<List<T>>>) -> List<T> {
        return List { value, next };
    }
}

#[cfg(test)]
mod tests {
    use super::List;

    #[test]
    fn it_works() {
        let node = List::new(1, Some(Box::new(List::new(10, None))));
        assert_eq!(node.value, 1);
        assert_eq!(node.next.unwrap().value, 10);
    }
}



Why and how to include Rust inside Python

In this short post, I'll be showing how to quickly write a naive implementation of the classic fibonacci recursive function. The amount of work being done without any cache can quickly grow and make you wonder whether your process is simply hanging unresponsive. Working with Rust to write up the fibonacci function and calling it from Python can be easily done and show drastic gains in performance.

To start, here is the Python implementation:

def fibonacci(n):
if n <= 0:
return 0
elif n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)

As for the numbers using IPython's %timeit:

In [13]: %timeit rust.fibonacci(35) 
59.2 ms ± 202 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [14]: %timeit fibonacci(35)
4.87 s ± 16.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

As one can clearly see, the difference is significant and definitely worth seeing if some refactoring and external calling of a Rust library is worth the effort. So without further ado, are the steps to call Rust from Python.

Step 1 - Create a Rust library

Create your library using cargo

SWEETUSER:tmp random1$ cargo new fib --lib

write the function and use macro to expose the function useable by FFI inside of lib.rs

#[no_mangle]
pub extern fn fibonacci(n: i32) -> u32{
if n <= 0 {
return 0;
}
else if n == 1 {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}

inside of Cargo.toml write


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

finally compile it 

SWEETUSER:fib random1$ cargo build --release

Step 2 - Call from Python

install INSIDE your env

(venv) SWEETUSER:fib random1$ pip install cffi

Now inside of Python

from cffi import FFI
ffibuilder = FFI()
ffibuilder.cdef("int fibonacci(int);")
rust = ffibuilder.dlopen("target/release/libfibonacci.dylib")

Step 3 - Profit

In [17]: rust.fibonacci(10) 
Out[17]: 55

As one can see this was extremely simple and hopefully can provide a basic guideline as to how useful Rust can be inside of Python. With the delta between the two seemingly identical functions being orders of magnitude apart, it is definitely worth a second look at that slow running process and how you can refactor it.

sources:

Rust Inside Other Languages

CFFI Docs