I have been learning rust for quite some time now and its structuring as a language is pretty funny. The promises are themselves a huge undertaking for the project. But for me, I have been interested, because of one simple reason, its fool-proofness.
"Fool proof?" you might ask, well yes! The language is practically an interesting take on the challenge to build a systems-level/C-type alternative to... well, C(C++ also kinda makes it into that category, but that debate is for another day). I have been into rust officially, for around 3 months now and it is interesting how easy it is to get tired and kick yourself out of learning it.
I think that is exactly why I am writing this log, because I want to share an interesting find I had while learning for myself. To take you back into the reasoning behind this post, I have been understanding the basics of this language with help from exercism, a language learning platform, akin to duolingo, but for programming languages. There exists an exercise problem on the platform which is pretty easy to implement in most other languages, but rust is a whole different game!
The n'th Prime problem
is pretty common in most intro to CS courses and has an easy to implement 'blueprint' algorithm. I implemented it with python, but then it felt like it could also be done with rust, also the fact that it is a simple transition process, I thought it would be a breeze once I decided to get started!
Here is the Python version of the program I wrote, before hand. I assume you known how to read pseudo-code, which makes it all the more easier to get started with Python :D
def nth(n):
arr = [2,3,5]
try:
return arr[n-1]
except:
n -= 3
i = 5
while n > 0:
i+=2
if is_prime(i):
n -= 1
return i
def is_prime(num):
for i in range(2, num//2):
if num%i == 0:
return False
else:
return True
print(nth(int(input())))
Let's see how I converted it into rust before reading the resultant code.
Python is an interpreted language, thus all code, passed to it line by line gets executed, then and there. Functions are also saved into memory and aren't executed till called. This is continued until we reach the end of all code lines. Rust is a compiled language, thus it requires to know which is the main function, code within which is executed at the starting of the program. Thus a program such as the quintessential hello-world is like this in python:
print("Hell, World!")
but in rust, becomes:
fn main() {
println!("Hell, World!");
}
Yes, the function is named println!
and the "!" is not a spelling mistake! This is so because in rust such commands, with an ending "!" are called macros. The print!
macro is also a valid option to print to the CLI, but we use println!
, since it adds a newline to the string passed into it.
Also fn
in rust is equivalent to def
in python. Since python is dynamically typed, it doesn't require, but still provides, type annotation. Which on the other hand, rust makes compulsary. The rust alternative code thus gets an argument with a type annotation in the code n:u32
, showing its 'unsigned 32bit integer' nature, which is also the function's return type(->
denotes the data-type of returned value from a function, it is not required for function that don't return anything). Thus the python def nth(n):
becomes fn nth(n:u32) -> u32 {}
in rust. Also, in rust, one defines types of a variable, statically, using the let
keyword. Though a variable in rust could be mutable, the type can't be changed, even though the assignment statement can also help the compiler infer the type of the variable from the value. Hence the following statements are legal:
let x = 0; // infers type on defenition and assignment of the variable
let y; // defines a variable
y=0; // initializes value of variable
let z:u8; // sets to type u8
let mut w = u32::new();
Within the program I could as well have done a simple if-else ladder to compare each input with a value in the array, but here I shall use a pattern matching tool provided by rust and called match
, similar to the switch-case in C/C++. Here is an introduction to the syntax, try to see if you can understand what it means:
let x =
match x {
/* probable_value eg:*/ 0 => println!("is equal!")// if matches, do this
/* use '_' to define the default behavious, in case the variable is not matched */
_ => println!("default behaviour!");
}
While rust automatically returns the value of a variable, or that which a function returned, when written without the use of a semi-colon, it is better to define a return
command. As I have observed from the statements written to supposedly return values from within the if-else/contioned code-blocks, this is not what happens. The compiler 'Clippy' returns an error stating it expected a ()
when you do the above, so beware!
fn nth(n: u32) -> u32 {
match n {
0 => 2,
1 => 3,
2 => 5,
n => trial(n),
}
}
fn trial(n: u32) -> u32 {
let mut num = n - 2;
let mut i = 5;
while num > 0 {
i+=2;
if prime(i) {num -= 1;}
}
i
}
fn prime(num: u32) -> bool {
for i in 2..num/2+1 {
if num%i == 0 { return false }
}
true
}