Monday, January 06, 2020

Learning Rust - Day 3 - Structs and Enums

I just completed the 3rd session of my journey of learning Rust. You can read other posts in this series by following the label learning rust.

This session saw me covering chapter 5 and chapter 6 of The <Rust> Book. These chapters are about structs and enums respectively.

This session was quite a breeze. This is due to the fact that the ideas presented in these chapters are really just about algebraic data types. And having encountered them before in Haskell, the session was more about seeing how the concepts is encoded in Rust, rather than learning new concepts.

In fact, I think the usual approach, that is prevalent in most Haskell literature of explaining these concepts i.e using Types, relation to set theory, data constructors etc is less confusing, less ad-hoc and makes things tie together nicely; especially when other concepts like pattern matching enters the picture. I guess taking time out to learn some Haskell wasn't a total waste of time after all :)

Having said that, there were still some things that stood out while going through these two chapters, and I enumerate them below. Yup, pun intended :)

When creating a struct you can't make individual fields as either immutable or mutable. You have to do this for the entire instance being created.

It is also possible to create a struct with fields unnamed. The term for this, in Rust lingo is tuple struct. And it seems the syntax for this uses () instead of {}. For example:

struct NormalPoint { x: i32, y: i32};
struct TupleStructPoint(i32, i32);

Since we are now in the terrain of systems programming, we can't afford to be totally oblivious to how this are put in the memory. <strike>When we have things like struct or enums they reside on the heap</strike> Not true. See here, and as we already know, a variable does not hold the value of such data. In Rust lingo, the variable is the "owner", and it holds ownership related information of which a pointer to the memory location on the heap is one of such information. If this is then the case, how then do we access things like the fields of a struct? Should we not first get a hold of the reference from the variable first, and then use that to access the fields? Well in Rust, no, because Rust has this thing called Automatic referencing and dereferencing which does this for you.

(&p1).distance(&p2); // Doing this is redundant
p1.distance(&p2); // Rust automatically does that for you

It is also possible to define function that is scoped to a particular struct or enum. This is called methods. The syntax is basically this:

impl StructOrEnum {
    fn your_function(&self) {
        
    }
}




Which I now sort of read as:

impl /*for an instance of: */ StructOrEnum {
  fn /*the following function:*/ your_function(&self) {
      
  }
}

When the defined function takes a &self as a parameter, such can be seen as instance methods: as you would call them in the OOP world. Without the &self,  then you have a static method. Although in Rust lingo, it seems static method are referred to as associated functions. Calling such associated functions involve the use of ::, eg:

struct AStruct;
impl AStruct {
    fn what_am_i() -> () {
        println!("{}", "I'm a struct")
    }
}

AStruct::what_am_i(); // prints "I'm a struct"

Also, there exist a if let syntax that can be used with pattern matching, when one is only concerning with dealing with one of the patterns and ignoring the rest: effectively turning it into an if...else construct. Basically this:

enum PrimaryColor {
    Red,
    Green,
    Blue}

fn is_red(color: PrimaryColor) -> bool {
    if let color = PrimaryColor::Red {
        true    } else {
        false    }
}

println!("{}", is_red(PrimaryColor::Red));

Still not sure how I feel about this syntax, Looks like something that shouldn't be used that much?

As it now seems to be customary of these journalling post, I have some info that I picked up that could be presented in a QnA format:

Q. How do I print out a struct or enum to the console?
     A. I either implement the Display trait, or the Debug trait. The Rust compiler can derive an implementation the Debug trait for me, I just need to place #[derive(Debug)] at the top of the struct/enum and then use {:?} or {:#} instead of {} in the println! macros.

Q. How do I create an instance of a struct from another struct.
     A. Use the double dot syntax. Since a code snippet is better than a thousand words, here you go:

struct Point {x:i32, y:i32}
let p1 = Point {x:0, y:0};
let p2 = Point { x: 1, ..p1 };
println!("x is: {}, y is: {}", p2.x, p2.y); // prints x is: 1, y is: 0

My next study session would be about modules in Rust. I am slightly apprehensive about this, as I have heard that this is one of the unnecessarily confusing part of the language. Finger's crossed, i'll definitely be journalling about how it turns out! :)

No comments: