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”