Rust feels like a distillation of the best work that came before it.
Platforms reflect their values, and I daresay the propagation operator is an embodiment of Rust’s: balancing elegance and expressiveness with robustness and performance.
So, what have been my experiences so far? I have spent the past 30 days reading Rust books and going through the Rust exercises at Exercism.io, benefiting immensely from the help of my mentors, especially Lewis Clement (thank you so much!).
Let me start with the positives:
.rev()for going in reverse, etc.)
ifreturns the value of the executed branch (so no need for the JS ternary operator
?:!); something I am already used to from Clojure
(Just for the record, I still strongly prefer Clojure and its interactive development and simplicity, though obviously the two have (mostly :)) completely different use cases.)
Perhaps my greatest struggle was satisfying the type checker and understanding why my types did not match. Some examples:
Vecinto chunks but there is no iterator that does that (in
std; there is a library for it); however there is the
.chunks()iterator for slices. Can/should I turn the Vec into a slice to be able to use it (and perhaps back afterwards)? What would be the cost of that?
Rev<Iter>>. (That's the price for this zero-cost abstraction.) See the type of the combinevecsexplicitreturntype function before it was simplified to
impl Iteratorfor another example.
once(1).chain(once(2))have incompatible types though logically both are
Iterator<u32>; same with
Iterator<String>but needed to return
HashSet<&str>and struggled to convert it somehow. (I had to give up on
.map(|w| w.to_lowercase())because that was what turned the
into_iter - what is the difference and how does it matter? What is the difference between
Iterator)?! Looking at the docs for
Vec I found this:
impl<'a, T> IntoIterator for &'a Vec<T> fn into_iter(self) -> Iter<'a, T> //Creates an iterator from a value. impl<T> IntoIterator for Vec<T> fn into_iter(self) -> IntoIter<T> //Creates a consuming iterator, that is, one that moves each value out of //the vector (from start to end). The vector cannot be used after calling this.
so based on whether I have a reference or a value (if I'm reading it correctly), I get a different result. It is hard to understand what this actually means / consequences without broader and deeper knowledge of Rust.
I understand ownership, moving, copying and borrowing in theory but do not grasp it and its implications fully in practice. I struggle to grasp when do I need
*x; and it gets even more complicated when I use some of the iterator API and end up e.g. with
&&&x (fortunately I have been pointed to
Iterator::cloned() to get rid of one level of
& though I would hardly find it myself; example of the problem:
assert_eq!(1, ["abc"].iter().filter(|s| **s == "abc").count());). This PostgreSQL example confuses me because it uses
&conn.query (perhaps because the latter is used in
for .. in and we don't want the for loop to take ownership of
conn and destroy it?). As a beginner I do not know which types implement
core::marker::Copy and thus are copied vs. moved. When do I want/need to use
I also struggled to understand when I need to dereference and when not because some methods were happy with a reference while other methods/operators required a value. From C I am used to that whenever I have a pointer I must use the dereferencing
-> instead of
. while in Rust it seemed to be somewhat random. (Presumably because a method is just a function that takes as the first argument self - or &self. So it is clear from the types - if you know and understand them.) It helped somehow when I stopped thinking in the terms of values vs references and starting to thing of owning vs borrowing.
Though there is a lot of great documentation online and it is awesome that the language docs include examples, one grievance is that the language docs are hard to navigate for me - when I search for
Copy I get 5 matches in Dash: one "S" (?), two traits (
core::marker::Copy and its re-export in
std) and two macros (
core::Copy). In this case it was the two traits that actually had the docs I have been looking for. Later I discovered that the "S" result was the best as it pointed to Rust reference section on Copy. When I search for "Copy" in the std crate, I get ±200 matches. I am sure that I will eventually learn to search and interpret results better but I am not there yet.
Understanding and distinguishing between builtin data structures, especially
Vec, arrays, and slices - they are all similar but different. (I understand now that Vec is on the heap and can grow while an array is fixed-size.)
Syntax - remembering what means what (especially macro syntax much more complicated than Clojure's for obvious reasons) (Clojure's syntax is trivial, the devil is in the semantics :))
Confusion by having the same functions and types in
core (until I understood that
std just re-exports the
core types for convenience).
I am learning a lot and it is great to do something completely different. I hope I get to write things in Rust so that I get deeper experience especially with the parts foreign to me. I wish Clojure tried to have so helpful error messages (though limited by the compiler's ignorance of the "types").