Where as with Rust, its as easy as:
The spectrum of typing
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:
- Type inference: means you don't have to declare the type in use every time
- Algebraic data types
- Pattern matching ~ or better yet exhaustive pattern matching!
- Lightly create and define new types that are compile time constructs
Static types aren't all the same
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
struct List<T> { value: T, next: List<T> }
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: