𧡠Understanding @rustlang's newtype idiom, when and how to use it. π
Let's imagine we are building a mini-database for students and professors, here is our starting point.
π
The idea is very simple:
* we keep track of professors using a u32 id
* we keep track of students using a u32 id
We are using a HashMap here, but eventually, this could be a trait with a real database behind it, so the ids are quite important here as this is how we find objects
Now, we want to implement course registrations. A student can register for the course of a professor.
Here is how the database looks now.
We've added a new course_registrations vector, and we use it to register students for courses and then check if they are registered.
So far so good, let's write an example and verify our code.
In this setup we have one professor: John, and two students: Bob and Sue.
Both Bob and Sue register for John's course, because John is amazing.
Now we write some code in main to verify our registrations.
But wait, we just added both Bob and Sue to John's course, but according to this Sue is not registered for John's course π€π€―
Let's zone in on the bug. πͺ²
The bug here is we mixed up student_id and professor_id in the implementation of is_registered. Since they are both u32, it's an honest mistake to make.
The fix is easy, we swap student_id and professor_id, but let's think bigger. Can we improve our code to avoid it altogether?
1/ Attempt 1: Add a type alias for student id and professor id
This improves the readability of our code, but if we go ahead and we make the changes in the rest of the code you'll see functionally there is still no difference.
The code still compiles when we mix StudentId with ProfessorId and we still get the wrong answer.
2/ Attempt 2: Add a new type for professor id and student id.
This syntax is a little different, but basically, we define a new struct, with one unnamed u32 field inside it. This is called a new type. One of ProfessorId, and one for StudentId.
Let's see what happens now.
Now we run the same code, but this time the compiler pinpoints our bug πͺ² and doesn't even allow us to run the code.
It also conveniently tells us exactly what to change.
And that was a quite long explanation of #rustlang's newtype pattern! π¦π
As the pattern itself is very easy to implement, I decided to focus the thread more on the motivation and how/why it helps. Hope this was helpful!
β’ β’ β’
Missing some Tweet in this thread? You can try to
force a refresh
Iterators are a convenient way to traverse collections. A collection contains multiple elements of the same type (think vector, hashmap, tree, etc.), i.e. it makes sense to traverse it.
Context: I started off with ggez + specs and a very basic understanding of #rustlang and the game I wanted to make. I decided to investigate macroquad+hecs as an alternative and ended up attempting a re-write. This is a thread about my attempt!
βοΈ How much did you actually rewrite vs reuse?
I started with an empty main. I asked myself how each thing was implemented before and if it still made sense. I reused some of the components I had from before but I also changed a lot. Some core ideas stayed and some changed.
β±οΈ How long did it take?
Judging from the commits and dates, I think it took about 12-14 hours of development. One thing to note is that a rewrite was only possible in this time because my understanding of both rust and the task have significantly improved since I first started.