Beginner experiences with Rust
Recently I’ve been learning Rust to get a better understanding of lower level systems languages. I’d heard good things about it, and programs written in it, plus it always came up as a most loved programming language on surveys, so I was curious to see what all the fuss was about.
I went through, what I understand is a fairly typical introduction to the language. I read the rust book through, watched a few YouTube videos, and then tried to put what I learnt into practice by creating a CLI app: analyse-json
Here are a few useful things that I found along the way that I initially struggled somewhat with. Ranging from the trivial, to some very annoying compiler issues…
chars vs str vs strings
Plenty of places in the docs/book made it clear the difference between str and Stings
let my_str = "str";
let my_string = "string".to_string();
// or
let my_string_2 = String::from("string");
However, when I needed a single char, it took me a while to reacquaint myself with the syntax which I had barely seen/used since it’s initial introduction in the data types section of the book
let my_char = 'c';
// Notice the difference in quotes ' vs "
Potentially, coming from python where these varieties are less impactful, I might have overlooked the significance of it until it became relevant
Owned methods
I found that I was so often implementing or seeing examples of methods that referenced self, that I forgot they could do anything else. However, methods don’t have to use references, they can take ownership of self and consume the value
impl MyStruct {
fn my_method(self) -> Vec<MyStruct> {
vec![MyStruct]
}
}
Structs with lifetimes
Whenever implementing methods for structs with a lifetime, ensure methods return values take into consideration the lifetimes they are referencing
struct MyStruct<'a> {
inner: &'a Inner,
}
// e.g. this
impl<'a> MyStruct<'a> {
fn index(&self, index: I) -> MyStruct {
MyStruct { inner: &self.inner[index] }
}
}
// should be
impl<'a> MyStruct<'a> {
fn index(&self, index: I) -> MyStruct<'a> {
MyStruct { inner: &self.inner[index] }
}
}
One way to more simply handle this can be to instead make use of Self
, which includes
the lifetime annotations
impl<'a> MyStruct<'a> {
fn index(&self, index: I) -> Self {
MyStruct { inner: &self.inner[index] }
}
}
into_iter() vs iter()
This one is complicated, so I’ll delegate to these great articles from stackoverflow.com and hermanradtke.com
What the hell does this turbo fish thing do?
fn my_generic_function<T>(func_arg: T) {...}
my_generic_function::<SpecificType>(...)`
The way I finally understood it was so think of SpecificType as a special type of
function arguments, for the <...>
section of the function, rather than the (...)
one.
It’s the value that will fulfill the contract specified by T.
This might become more clear with an additional example:
fn my_generic_function<T, K>(func_arg: T) {...}
my_generic_function::<SpecificTypeA, SpecificTypeB>(...)`
Where the specific version of the generic function we end up using has T
replaced
by SpecificTypeA
and K
replaced by SpecificTypeB
Code structure
Once you’ve got your hands dirty and written a reasonable amount of your own code, it can be a good time to review the rust api guidelines to get some ideas to tidy up your interfaces, and refactor it all to be a bit more “rusty”