My usual approach to learning a programming language has always been to just use it. I don’t really learn by going through hours of courses or reading hundred of pages worth of books and articles. If I need to get something done, and a language seems fit for the case, I just try to use it and learn it along the way. I always have the official documentation as my trusty sidekick.
This method works wonders if the language is relatively simple and similar to the ones you already know. It still works either way but if the target language is vastly different, the journey is much less enjoyable. Coding is more fun if you’re competent and know what you’re doing without having to look everything up.
Rust has been on my radar for a while now. I’ve heard plenty about it—both good and bad—and even used it for a couple of small tasks. Again, I’ve never actually learned it, but even with this limited experience, I can tell I won’t be able to take the usual path to master it.
Rust is simply too different from the languages I’m used to. To get anywhere with it, I’ll need to take a more structured approach—whether that’s a course, a book, or something similar. I’ll actually have to sit down and learn it.
It just so happened that I recently came across Google’s Comprehensive Rust course, and I figured this would be a good opportunity to go through it and finally learn Rust. So here I am, documenting my study notes as I go along.
Day 1 & 2
I find it interesting that the course highlights use of (declarative) macros for variadic functions, since the official documentation doesn’t seem to emphasize that perspective.
Rust uses macros for situations where you want to have a variable number of arguments (no function overloading).
My initial assumption was that Rust would be able to infer an appropriately sized type for untyped, unsuffixed integers and floats. Turns out it’s a little more complex (or simpler) than that.
- Rust first tries to infer the type from surrounding program context. When there’s no context available, it falls back to
i32
for integers andf64
for floats. If the number doesn’t fit within the bounds ofi32
, it just errors. - RFC 212 documents the rationale for defaulting to
i32
overi64
, it’s becausei32
is cheaper, more performant and C’sint
like.
- Rust first tries to infer the type from surrounding program context. When there’s no context available, it falls back to
Rust’s method syntax makes a lot more sense if you see it as a shorthand.
self
isself: Self
&self
isself: &Self
&mut self
isself: &mut Self
The fact that Rust compare references by the value of the things pointed to, and NOT by the addresses themselves only makes sense if you think of them as references and not addresses like in C or Zig.
Rust is like 90% traits (exaggerating) and they should be explored more in depth.
Sized
trait is essential to understand trait objects, and had I known about it earlier, i would have had an easier time understanding string slices too.dyn Trait
uses dynamic dispatch via a vtable (virtual method table), while generics (includingimpl Trait
) use monomorphization for static dispatch.- At runtime,
&dyn Trait
is a fat (wide) pointer: one pointer to the object and one to the vtable. The vtable resolves method calls, asdyn Trait
is unsized, requires indirection via&dyn Trait
orBox<dyn Trait>
.
Operator overloading via traits in
std::ops
is probably my least favourite feature about Rust. Also, I’d rather have!
error out on non boolean types than overloaded behaviors, e.g. bitwise negation on integers.I’m not sure constant hoisting is an intentional language design or rather a side effect of multi-pass compiler inlining them. I’d like to think it’s the latter because I don’t see any substantial benefits to it being a language feature than maybe make a good interview trivia.
- I’m not convinced on the necessity of
as
keyword for casting primitives, especially because what it does depends on the types converting to-and-fro. The only thing I can see it being useful for is pointers but the documentation seem to recommendcast
overas
even for them.
Day 3 & 4
- I think the memory management semantics can be succinctly summarized like this. At least this is how I see it.
- “Copy semantics” is just “pass by value”, the value is bitwise copied.
- “Move semantics” is essentially “clone & invalidate”.
- Rust will implicitly copy values marked with
Copy
. But,Copy
is an un-implementable marker subtrait ofClone
. Copy
andDrop
are exclusive. Exclusive references aren’tCopy
either.
- Why does
Drop
take&mut self
and notself
?Drop
doesn’t need to own the value, Rust will automatically insertdrop()
at the end of each scope on owned values that implementDrop
. ImplementingDrop
is most commonly done just to do some extra stuff before getting dropped.- In fact, if it was owned
self
, the compiler will have to special-case it to not insert adrop()
which would cause a recursion. - Then why is it not
&self
?Drop
is meant to be an inherently mutating operating, potentially changing the internal state of a struct for example.
- I wish re-borrowing was explained better in the course. There’s only an example provided and it feels incomplete. It’s not very apparent that it only works in joint with non-lexical lifetimes.
- Subtyping with lifetimes gets confusing because the typical subtype example
Cat
andAnimal
, but the example is not that aCat
is a subtype ofAnimal
just by nature. That’d be an example of inheritance. The example is thatCat
can be considered a subtype ofAnimal
if and only ifCat
can be used anywhereAnimal
can be used.- The definition is,
A
is a subtype ofB
ifA
is substitutable anywhereB
is used. - The lifetime
'long
, if contains the region'short
completely, can be used as a substitute of'short
because it lives longer. Therefore'long <: 'short
.
- The definition is,
Deep dives
I’m skipping the last four deep-dive sections (Android, Chromium, Bare-Metal and Concurrency) due to them being slightly more advanced. I do want to visit them back later after I’ve gotten a bit more comfortable with the language. Otherwise, it’s a bit too much to take in all at once.
That’s it. This was fun.