
A half-hour to learn Rust - xrd
https://fasterthanli.me/blog/2020/a-half-hour-to-learn-rust/
======
talkingtab
This is the most useful introduction to a language I have ever read. Often
language introductions produce a "Wall of Complexity" and I failed in my last
two attempts learning Rust failed because of that. This is just great.

Be warned, the "half hour" part is probably a bit like "99 cents" as a price
tag. I've already spent more than that, but it is time well spent.

Thanks for writing this!

~~~
galfarragem
You might like this:
[https://learnxinyminutes.com](https://learnxinyminutes.com)

~~~
nobleach
I also like Derek Banas videos
([https://www.youtube.com/channel/UCwRXb5dUK4cvsHbx-
rGzSgw](https://www.youtube.com/channel/UCwRXb5dUK4cvsHbx-rGzSgw)) for this
same quality. He doesn't do many "deep dives" into the languages, but he does
a fantastic job getting your feet wet with very common tasks... much like
these learnxinyminutes pages do.

------
kzrdude
Underscore is not exactly "throw away" but rather it is exactly "don't bind to
variable". The difference becomes evident with the statement:

    
    
        let _ = x;
    
    

This is a no-op and the value remains owned by the variable x, and is not
thrown away.

~~~
fasterthanlime
I debated which terminology to use and thought "throwing away" was more
intuitive, especially if you've never heard of "binding" before.

It has its limits though - in your example I'd say you're throwing away the
result of evaluating "x", just as if you did:

    
    
        x;

~~~
kzrdude
I don't agree, because we can try the two following programs, and one of them
does not compile. It compiles with underscore.

1:

    
    
        let x = String::new();
        let _ = x;
        println!("{}", x);
    

2:

    
    
        let x = String::new();
        x;
        println!("{}", x);   // ERROR: Use of moved value x.
    
    

And this is why I said that let _ = x; is a no-op. :)

~~~
fasterthanlime
Huh, TIL! Had no idea `x;` actually moved `x`.

~~~
AnimalMuppet
But where is it moved _to_? Who owns it now? Or is it just lost?

~~~
kzrdude
yes, it is dropped

~~~
runarberg
Ahh, I remember a while back reading a blog where the author talked about
`std::mem::drop` being their favorite standard library function because they
used this effect. It is literately defined as:

    
    
        pub fn drop<T>(_x: T) { }
    

[https://doc.rust-lang.org/std/mem/fn.drop.html](https://doc.rust-
lang.org/std/mem/fn.drop.html)

~~~
e_proxus
So is drop like free, or is it something else?

~~~
cyphar
Yes, it's effectively like free -- though C++'s "auto" is probably a closer
idea.

The main difference to C's free is that you simply cannot double-free a value
(without using unsafe). That's because a value is only dropped when it falls
out of scope -- and since it values only have one owner (and references must
not outlive values) there cannot be any dangling references after it's
dropped.

The documentation for std::mem::drop is explaining that dropping a value is a
property of the language and scoping rules -- thus there is nothing for the
function to do other than take a value and let it go out of scope.

------
esjeon
Nothing related to the tutorial itself, but I've seen many Rust basic
tutorials recently, and this sorta remind me of Haskell - especially its
Monad.

The Haskell community once was flooded with Monad tutorials[1]. People kept
trying to put meanings on this mathematical construct, and had come up with
tons of different ways to describe it. The problem is, this simple thing can
fit into so many different places, so people couldn't possibly run out of new
ideas. This oversaturation only confused newcomers even more, so the community
concluded not to write any more Monad tutorials.

While individual tutorials enrich the community in general, it's more crucial
to have a few good documentation that kill the need for more tutorials (e.g.
MDN + W3School), so that efforts can be redirected into more productive
things. Rustonomicon is one good documentation, but it always falls few steps
short in practice. That's probably why more tutorials are being written, and
why they get upvoted well in both HN and Reddit.

[1]:
[https://wiki.haskell.org/Monad_tutorials_timeline](https://wiki.haskell.org/Monad_tutorials_timeline)

~~~
whatshisface
The dust is settled and the fight is over. Which monad tutorial won "best
monad tutorial?"

~~~
jerf
None of them. The concept is fundamentally flawed. It's as if everything you
ever read about C was all about bitwise manipulation operations, on and on and
on about bitwise manipulation, to the point not-C programmers think the
language is primarily about bitwise manipulation and people start porting
bizarre misunderstandings of bitwise manipulation into other languages and
claiming they're just like C now, when it's just a part of the language that
you'll pick up over time. Not a perfect metaphor but close enough.

~~~
isoprophlex
However, the c community never felt like going on and on and on about bitwise
manipulation... Anyone have any idea why people get so hung up about
explaining monads?

~~~
blub
I've seen that some programming communities focus on writing software, while
others focus on talking about software.

C and Haskell are two good examples of this dynamic, although C is used in so
many different projects that "community" only loosely applies.

~~~
isoprophlex
Interesting, maybe the Sapir Whorf hypothesis is much more relevant for
programming languages than for natural languages; maybe the language shapes
the things a human can comfortably express, and this shapes the communities

[https://en.m.wikipedia.org/wiki/Linguistic_relativity](https://en.m.wikipedia.org/wiki/Linguistic_relativity)

------
krebs_liebhaber
The way I like to think about the expression / statement divide in Rust is
that _everything_ is an expression, and the semicolon is an operator akin to
`ignore` in F#; it takes any operands, disposes of them, and returns the unit
type `()`.

Rust in general is just such a pleasant language. It can be a bit tedious to
work with sometimes, but that's usually because the problem in question is
tedious in and of itself, and you didn't even notice all of the little screw-
ups that could occur until you saw how Rust dealt with it.

------
pgt
Reminds me of Learn X in Y Minutes:
[https://learnxinyminutes.com/docs/rust/](https://learnxinyminutes.com/docs/rust/)

------
svnpenn
Has anyone struggled with the lack of HashMap literal?

[https://github.com/rust-lang/rfcs/issues/542](https://github.com/rust-
lang/rfcs/issues/542)

I just cant bring myself to doing something like this:

    
    
        let m1: HashMap<&str, i32> = [("Sun", 10), ("Mon", 11)].
        iter().
        cloned().
        collect();
    

[https://doc.rust-
lang.org/std/collections/struct.HashMap.htm...](https://doc.rust-
lang.org/std/collections/struct.HashMap.html)

~~~
dmit
Would a macro work ok for you, like the inbuilt vec! one? There's a crate for
that named maplit: [https://docs.rs/maplit](https://docs.rs/maplit)

    
    
        let map = hashmap!{
            "a" => 1,
            "b" => 2,
        };

~~~
gameswithgo
one of the fun things about Rust, is any time you think "man I wish there was
some syntax to do this thing in Rust" you can make a macro to make it reality,
usually one already exists.

------
131hn
Only, it’s more one hour and a half rather than half an hour. Excellent
quality stuff nonetheless

~~~
csomar
It's only an half an hour if you are familiar with these concepts (generics,
traits, pattern matching, error propagation, etc...). Some concepts are
exclusive to Rust (lifetimes/ownership), so I doubt you can get it in 30
minutes if you have never written code in Rust before.

~~~
cormacrelf
I’ve seen this guy’s posts a few times lately, he needs to learn to edit
things down. What he’s saying is good, but it is extremely digressive, and the
digressions never seem to return to the original point. I’m not the audience
for this so I don’t have that much time to spare reading it, but I can’t
imagine that style working well for an introduction to a language.

~~~
fasterthanlime
I respectfully disagree but - can't make everybody happy.

There's plenty of introductory articles to Rust in the style you desire :)

edit:

To expand a little bit, the digressions aren't just about me "getting
distracted" while writing - they're very much on purpose. I always try and
write pieces that expand in many different directions, because there's so much
to discover, always.

Some folks come to an article wondering why Rust has two string types and end
up spending an hour on Wikipedia reading about legacy code pages - and I think
that's great.

My way of getting people interested in something is never a top-down, present-
the-bare-minimum way, it's always about showing how what we're discussing is
connected to a lot of other things, which are also fascinating and that you
should check out if you want!

~~~
cormacrelf
I think I'm talking more about your Mr Golang piece, but now that I have your
ear, I guess may as well. I am less concerned with the existence of
digressions than the way they're introduced. When I say needs editing, I mean
that because I cannot navigate the piece properly, it takes longer to
understand what you're getting at if I want to, so it seems like it's too
long. Editing down is only one solution to that.

An example of this is when I was really confused reading about path extensions
and non-UTF8 paths. I was promised by Cool Bear that you had a point to make
using an example about a stat call, but you were no longer talking about stat,
and yet you were still writing as if it was central to your stat discussion.
So instead of expanding, I thought you just couldn't express what was wrong
with the stat call. I nearly gave up waiting! And I was surprised that, in the
end, you could (and put it quite well). Unfortunately, there are thousands of
thinkpieces out there that never make the point they promise to, instead just
dumping information at you and hoping it hits you like it hit them. So I am
trained to close the tab when that is happening.

It might help to state why you're going to digress before you do, with a
promise to return, so that people who are hooked can be confident you'll
eventually make your point (and may skip back and forth to digest your
argument again without interruption). If nothing else, you are making a
promise to yourself to structure things in a way that is friendly to the
reader.

Rather than making the piece less exploratory, adding signposting helps get
the reader into the mindset you describe in the last sentence there.
Otherwise, they are not sure whether to be interpreting what you're saying as
a core argument or some side discussion. It hurts both the exploratory and the
argumentative qualities of the writing if one is confused for the other.
Without making it clear, the argument runs like my first para above:
unnecessarily long-winded and questionably relevant, with no exploratory
levity. With good signposting, you get to nail both. It's about two sentences'
and two headings' difference, maybe a little shuffling around.

Hope that helps, looking forward to your next one.

~~~
fasterthanlime
The Golang piece is definitely subpar, it was quickly thrown together in half
a day, didn't expect it to be spread so widely haha.

For other articles there's usually a few days of research, and 1.5 days of
writing and editing, then a lot of touch-ups in the following weeks/months
responding to feedback.

Thanks for the more detailed criticism though - I agree with the general
sentiment, and haven't solved the navigation problem yet!

------
dmitriid
This is very good but... Description of traits suddenly dumps references,
mutable references, reference bindings, dereferencing with zero explanation:

\--- start quote ---

Trait methods can also take self by reference or mutable reference:

    
    
      impl std::clone::Clone for Number {
        fn clone(&self) -> Self {
            Self { ..*self }
        }
      }
    

\--- end quote ---

What? What does this even mean? What is <asterisk>self, and _why_?

Too many questions, not nearly enough answers.

~~~
dilap
If you have a struct of type T and an instance of that struct t, you can
create a new instance using the syntax

    
    
      T { ..t }
    

which means "a new T with all the fields set to their values in t".

Inside the trait "Self" is an alias for "Number".

"&self" is shorthand for "self: &Number", i.e., a reference to Number.

To dereference a reference, prefix with

    
    
      *
    

So:

    
    
      self
    

has type "&Number"

and

    
    
      *self
    

has type "Number".

Thus,

    
    
      Self { ..*self }
    

is creating a new instance of Number with its each set to the same value as
self.

 _Disclaimer!_ everything I know about rust I learned 2 days ago from this
blog post, so I might be wrong :-)

------
unixsheikh
What a nice approach to presenting the language in a clear cut way. We should
have this for every language out there.

~~~
abledon
ive been perusing a bit of rust code for the past 2 years, reading an article
here and there....always heard it was a complex and low level , next
generation C language. comparing it to go etc... but never wanted to learn
it... This article is _so_ simple in its delivery. It really got excited in
the 'beauty' presented by each simple chunk.

------
owaislone
This is great write up. I wish every language had this on their official page.
I'd personally love one for C++.

------
5Qn8mNbc2FNCiVV
This is so good. I would love this kind of "learning" for every language. It's
definitely not beginner friendly but for anyone that already knows a language
this is a super introduction and quick start, maybe even tipping point to
start using the language.

Only remark: If I had anchor tags at specific points creating a reference
would be easy, this is referenceable material.

Great job though, I am amazed at the quality of this. Hell yeah!

------
michaelangerman
I am really happy I found this blog post. If you like this one you should
check out some of his other posts. He has some amazingly insightful other
posts on Rust including some very in depth topics. Also, a great comparison of
Rust and Go. This is also why I love Hacker News. I would have never found
these posts if this one had not made it to page one.

~~~
donmcronald
I want off Mr. Golang's Wild Ride [1] is a great read. The part about how
different OS file permissions are handled in Go vs Rust is great. Even though
I don't know Rust, I thought the Rust approach looked easiest since it's
accurately represents the underlying systems. It was really surprising to see
that Go's motivation in glossing over the complexity is to keep things simple.
Is a half working implementation really simpler?

1\. [https://fasterthanli.me/blog/2020/i-want-off-mr-golangs-
wild...](https://fasterthanli.me/blog/2020/i-want-off-mr-golangs-wild-ride/)

------
jariel
Every young language must have tutorials like this.

Start with the utter basics but move quickly through the common issues.

More than anything, these kinds of tutorials make it possible to at least
start grasping the rest of the documentation.

I find with so many funky languages, the little examples aren't so hard, but
there's too much 'unknown' syntax in the way to make sense of it.

These are essential.

------
clarry
I want books written like this. I've had too many books where I die of boredom
over and over again in the first 150 pages and then I never read them again.
It's like there's some rule saying books must be super wordy.

~~~
TheRealPomax
That rule is usually called the publication contract, and it usually literally
says how big the work needs to be, even if you can say it better in fewer
words/pages/chapters/etc.

~~~
kanakka
TIL. This seems to be waste of paper/time for consumers. What is the incentive
for publishers by preferring wordy version than terse one? Is it because they
afraid of being perceived as not worthy enough to be a book?

~~~
dilap
Holdover from another era. Pre-internet, we had more time to spend w/ books,
and it was harder to gain context to make things make sense -- a little hand-
holding and digression was more appropriate then.

------
dmitshur
I really enjoyed this, and it also inspired me to try a quick experiment: what
would the same blog post look like for Go?

I tried it and wrote
[https://dmitri.shuralyov.com/blog/27](https://dmitri.shuralyov.com/blog/27).
It was a fun exercise to go through.

~~~
bogomipz
Thanks for sharing. I enjoyed this. Could you elaborate on your closure
example though? This was the only one I didn't understand. The explanation is
a bit terse = "Closures are just variables with a function type with some
captured context." Are these anonymous functions? What is calling the
anonymous function if so in main? Cheers.

~~~
dmitshur
Thanks for feedback. I’ll try to clarify that section.

------
csomar
Numbers could also be differentiated in Rust. For example, to make sure your
"16" is an _i32_ you could write

> let x: i32 = 16i32;

Otherwise, it's a very comprehensive article. The only missing parts are the
_async /await_ keywords and the sync primitives (like Mutexes/RwLocks).

~~~
GordonS
C# has a similar feature, but it's less verbose, e.g. "16l"/"16L" is a long
(64-bit integer), "16u" is an unsigned, 32-bit integer etc.

I find the rust syntax a little difficult to read. Take "16i32" for example -
it's not immediately clear which part is the actual value.

~~~
fasterthanlime
One neat thing Rust let you do is put underscores in number literals. It's
great for readibility:

    
    
        0xFFFF_FFFF // easier to count than 8 Fs
    

And you can use that with type suffixes:

    
    
        let a = 255_u8;
    

Although in that case, you'd probably either omit u8 entirely, letting the
compiler infer the type of 'a' from usage, or use a colon to give it ane
explicit type.

Type suffixes are especially useful for arguments to generic functions:

    
    
        write_le(12_u32);
        write_le(255_u8);

~~~
GordonS
You can do the same thing with C#, which is great for groupings, e.g.
"2000000" becomes "2_000_000".

As a rust noob, I didn't realise rust supported the same syntax, or that you
could use it to separate the type from the value (which in rust's case, I find
really helps readability).

------
jononor
Nice. What is dont understand is why is the second Vec2 used in this example?
It introduces x and y, which are of type float right, not Vec2?

let v = Vec2 { x: 3.0, y: 6.0 }; let Vec2 { x, y } = v; // `x` is now 3.0, `y`
is now `6.0`

~~~
wizzwizz4
Because you're ---unpacking--- destructuring a Vec2.

The most common form of this idiom I've seen is:

    
    
        if let Some(x) = foo() {
            println!("{:?}", x);
        }
    

where fn foo() -> Option<_>, i.e. foo returns either Some(_) or None. (if let
is used to account for the possibility of None; it's technically different
syntax to let, but very similar.)

Here, x isn't a Some (a variant of Option). It is, however, representing a
value inside a Some, which we want to get out. Likewise with your example; we
want to destructure from a Vec2, so we specify a Vec2 (with identifiers
instead of data) on the left hand side and the data on the right hand side,
and it takes the data out and binds it to the identifiers.

------
dilap
This is fantastic! Using it, I was finally able to write my favorite toy-
problem in Rust (scoring a boggle board). Rust solution came out 25% faster
than my (I thought) highly-optimized C++ solution, wow...

------
jquave
I've been writing Rust for about 2 years, I read this and learned all kinds of
stuff I still did not know. Fantastic job on this! I am now a bit upset that
everything isn't taught this well :o

------
harel
I wish all languages had this kind of page as a requirement of the language
manual. Author, I will hold your beer...

------
zimbatm
What does `type Output = Self;` for the `std::ops::Neg` trait mean? It wasn't
explained on the page.

~~~
Lev1a
IIRC that's called "associated type" and is mostly used for shortening the
function declaration inside the trait through using e.g. "Self::Output".

E.g. the definition of the real std::ops::Neg trait: [https://doc.rust-
lang.org/std/ops/trait.Neg.html](https://doc.rust-
lang.org/std/ops/trait.Neg.html)

~~~
steveklabnik
It’s not about shortening things, it’s actually about semantics! Choosing to
make something have a type parameter vs an associated type depends on what
you’re trying to accomplish.

------
Karupan
I found this extremely helpful. I’ve always been interested in Rust but never
got around to it cause most of the source code I’ve read on GitHub was
overwhelming. This is a nice walkthrough to get someone like me interested.
Great work!

------
qgadrian
Loved the approach, no time wasting and you can dig on things are not clear
for you.

------
j1elo
Close to the beginning there is a phrase you'll want to review:

> _If we we really we wanted to annotate the type of pair, we would write_

Ok understood, we're us and we're all together in reading this! :-)

------
127
For a clickbaity title, it's surprisingly useful. Of course, the time to read
it might take half an hour, but the time to really absorb it might take two
weeks.

------
perlpimp
this is the style that let me get into python reading a python book by new
riders within 3 days. i replace perl based module within a week of reading.

this: teaching material should give immediate confidence to apply learned
material and - refine it later.

excellent write up

------
webew
Does anyone know of a similar tutorial for C, C++, Python, et al?

------
signa11
is there a poignant guide to rust ? something similar to ‘learn you some
erlang for great good’ ?

------
pansa2
Typo: “If we we really we wanted”

------
plutonorm
Next up: Learn mandarin in 0.1^99999999999999999999 seconds.

------
fenk85
Very nice! first impressions is that it is very similar to Kotlin which i love
working in, seems modern languages are converging on common set of niceties

------
sgt
Serious question; why aren't we all using Rust?

~~~
zimpenfish
I think for a lot of use cases, the safety and security and whatnots you get
from Rust just aren't required and people already have a large time investment
in Python/Go/Java/etc.

Certainly I can do pretty much everything I need to with C/Python/Go because I
accept the tradeoffs and know enough to work around the safety/security
issues.

(But Rust is on my list of things to get around to in 2020.)

------
jokoon
> let x: i32 = 42;

I'm sorry but this notation will always make me scream. C is so much simple:

int x = 42;

not "let", no colon, and no ambiguous "i32 = 42"

~~~
Sharlin
You mean, like when declaring an array:

    
    
        int[4] arr; // oops, doesn’t compile
    

Or a function pointer:

    
    
        int (*)(int) fptr; // oops, doesn’t compile
    

So much for the simplicity of C declaration syntax.

~~~
jstimpfle
One can make the argument that the C declaration syntax _is_ simple, because
the rule to make a declaration is to simply follow a type name by an
expression where the declared variable is used. The fact that you write
"int[4] arr" shows that you don't know how it works (which is not a criticism;
it's just not well known how it works).

The correct way is to write

    
    
        int arr[4];
    

and to interpret it as "arr[4] is an int" (which is only a slight lie because
arr[4] is undefined if arr is a 4-element array).

How do you declare an array of pointers? Again, you write

    
    
        int *arr[4];  // array of 4 pointers to ints
    

because in C expression syntax, "* arr[4]" means to index into the array first
and then to dereference. If that is an int, it means that arr[4] is a pointer
to an int, and consequently arr is an array of pointers to ints.

If you want a pointer to an array of ints instead, do this

    
    
        int (*arr)[4];  // pointer to an array of 4 ints
    

again, because that's how regular C expressions work. Functions are (mostly)
not an exception:

    
    
        int myfunc(int x);
        int (*myptr)(int x);
    

which is to say that "myfunc(x) is an int" and "(* myptr)(x) is an int", i.e.
myptr is a pointer to a function that takes an int and returns an int.

Note that in the beginning (i.e. K&R C, pre-1989) the way to declare functions
was consistent: Declarations had to be

    
    
        int myfunc(x);
    

i.e. there was no types in the argument lists. The types in the argument list
appeared, I believe, after Stroustroup added them to C++ in order to improve
type-safety.

~~~
will4274
> the rule to make a declaration is to simply follow a type name by an
> expression where the declared variable is used.

Not really. You can't just use _an_ expression, you have to use a specific
expression. For example, * ppX is a perfect valid expression for a pointer to
a pointer named ppX, as in:

    
    
        int **ppX;
        if (*ppX == NULL)
    

So you need to use an expression where the declared variable is used _that
results in a non-pointer type_. And then, there's actually more to it... only
certain types of expressions are valid. E.g. this isn't a valid declaration
for a pointer, even though it's valid as an expression:

    
    
        int pX->;
    

I find that basically anytime somebody tells me that C rules are to "simply
[...]", they've inevitably ignored a whole bunch of cases. Your post is no
exception.

~~~
jstimpfle
> So you need to use an expression where the declared variable is used that
> results in a non-pointer type.

I think you are wrong here, you can't even make something other than what
"results in a non-pointer type". Because by definition, you're making the type
to the left with the expression. (That could still be a pointer type if it is
a typedef'ed type; such as "typedef int * intptr; intptr x;" but I don't think
you meant that by "pointer type" [0]).

And your first example is perfectly syntactically valid (other than missing
the conditional statement that must follow the if-condition).

And no, "pX->" is not a valid expression. Was that a typo?

And yes, only a subset of expressions are valid. Basically, the expressions
that you can form by applying subscripts, (x[3]), dereferences (* x), and
function calls. Because, a declaration like "int x + 3;" or even "int x + y;"
just doesn't make sense. I don't see a problem there.

Btw. I'm not saying that C as by the current standards is super
straightforward and pure. It's definitely not, and C does actually have a lot
of historical baggage that makes our lives a little harder. I'm just
explaining the underlying unifying principle, which IMHO is actually nice. And
honestly it seems you, too, are still confused because there is just a lack of
clear explanations about C declarations. That principle should be much more
well-known, and almost all problems that novices have with declarations are
unnecessary frustration that they wouldn't have if someone would have told
them the trick.

[0] By the way, typedef is another thing that seems to be super obscure, while
it is extremely simple: It's just a keyword that modifies declarations to
declare an alias for that type, instead of a (named) variable of that type.

