Been Rusting in a Ruble

In the past 3 years, I’ve been learning Rust in Decembers for Advent of Code, and each time, I learned something new.

In the most recent Advent of Code (2019), I did not finish all the challenges, but I enjoyed it a lot! My early decision to modularize some repetitive function really paid off in the later puzzles.

The most important concept of Rust language is borrowing. After things have been borrowed, it must be returned. As simple as this concept is, there may be lots of times one get frustrated by the compile errors of the Rust compiler. Oh, Rust compiler is so strict that Rust beginners will be very relieved if their codes compiles!

Moved

The “trap” that I most often fall into is due to using a moved variable in a loop. This is still somewhat related to borrowing (they’re both about memory ownership). Even if you’re not familiar with Rust, take a look at this piece of code:

fn main() {
  let abc = vec![1, 2, 3];
  let mut res = vec![];
  for i in 0..3 {
    res.push(abc);
  }
}

The gist of this code is that it declares a vector (array) with values 1, 2, 3 and assign it to abc. Then I want to push 1, 2, 3 to each of res, making res a 2-dimensional vector.

But we’re slapped with a compile error.

^^ value moved here, in previous iteration of loop

lovely compile error message from Rust compiler

The error here means that when i = 0, it tries to push abc into res (by reference), and when i = 1, it tries to push abc into res (by reference) again. This is strictly not allowed!

To mitigate this error, I simply have to clone abc into multiple copies before it can be moved into res. It makes sense. Each element inside res should be its own copy (and not sharing a same location in the memory).

That was the error I faced a lot while beginning to learn Rust back while doing Advent of Code 2017.

Module

In Advent of Code 2019, I learned some more advanced stuffs of Rust language. One of them is Rust’s module system. In the previous iterations of Advent of Code, I struggled in code reuse. If I want to reuse a function on another file, I copied those codes. That is not a good practice. In the 2019’s edition, I resolved to learn Rust’s module system properly.

In JavaScript (ES2015+ specs), one has to export a function in a file so that other file can import it. To call a function from another file, one has to declare the import and also the path to the file. It makes perfect sense.

Given the folder structure below:

/*
- src
  +- abc
  | +- def.js
  +- xyz
     +- 123.js

*/

In abc/def.js, I could call the hello function by importing it from ../xyz/123.js. The import is declared on that abc/def.js file.

// src/abc/def.js
import { hello } from '../xyz/123.js';
hello();

// src/xyz/123.js
export function hello() { alert(42); }

In Rust, this is not the case. In Rust, each folder/file is seen as a module. Therefore, if a file inside a folder needs to import things outside its folder, it needs to be declared at their lowest common ancestor (I’m actually not really sure about this, so CMIIW).

Given the folder structure below:

- src
  + main.rs
  +- abc
  | +- mod.rs
  | +- def.rs
  +- xyz
     +- mod.rs
     +- one.rs

So I would want to achieve these:

  • In main.rs, I want to call a function “test()” declared in abc/def.rs
  • In abc/def.rs, in that test function, I want to call a function “hello()” declared in xyz/one.rs

To achieve those, I need to:

  • Create specially-named file called mod.rs in abc folder and xyz folder. This is so that the functions created in the other files on those folders could be reused outside the folder.
  • On top of main.rs, declare mod abc;. Okay, still makes sense, since I want to call function declared in that abc folder.
  • On top of main.rs, declare mod xyz; too! This is the part that isn’t very intuitive for me.

So the files and their contents are like this (check it out at repl.it):

// src/main.rs
mod abc;
mod xyz; // <-- I need to declare "mod xyz" even though it is "abc" who will "import" functions from xyz module
fn main() {
  abc::def::test();
}

// src/abc/mod.rs
pub mod def;

// src/abc/def.rs
use super::super::xyz::one;

pub fn test() {
    one::hello();
}

// src/xyz/mod.rs
pub mod one;

// src/xyz/one.rs
pub fn hello() {
    println!("Hello");
}

So in Rust, mod.rs is a special file, I guess it i similar to how “index.js” is special in the JS world. But the thing that is not intuitive to me is that we have to declare “mod xyz” too in main.rs. I’m not sure why too.

Conclusion

In this post, I have described two lessons that I learned over the years while participating in Advent of Code using Rust although I haven’t been able to grasp everything fully. I hope I would use Rust more often and understand it better.

P.S. The post title is the first line of Coldplay – Hypnotised.

P.P.S. I started this post in early January 2020 and has been stuck in draft for a few months. I picked this up again recently and since I don’t think I have any more content to put, I quickly polished and published it. πŸ˜…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.