93 comments

[ 2.8 ms ] story [ 162 ms ] thread
Alternatively, my video introduction to Rust should be live soon: https://www.youtube.com/user/DevFactor Always great to have a few viewpoints.

I 'kudos'ed your post though - because I thought it was very nicely written!

I'd appreciate it if you provided a bare link to your channel instead of one that pops up a Subscribe modal.
It definitely feels a little long winded for a person with some background already in systems langauages like C. Do you plan a TLDR for systems language people?
A Redditor put it best:

    > My only concern is that lot of the motivating of the ownership
    > system seems to be directed at C/C++ programmers, but at the same
    > time you're explaining pointers and memory management as almost new concepts.
I think this is a weakenss of this draft, yeah. I want to make it accessible to non-systems people, but also okay for systems people. In the current, actual intro, I treat everything at a high level, looking downard occasionally. This version is more about starting down and looking up, but it might be not enough detail for the non-systems crowd (there's still tons of implicit knowledge here, even though I tried to spell it out) and a bit tedious for the systems crowd (who have intenrnalized much of this already.)

Writing is hard.

I definitely like the approach of starting with ownership, that's definitely a good thing. I feel the current draft places too much emphasis on implementation details of the stack/heap that I'm not sure will help users understand ownership better.

For example, going through line by line leads to a slew of text that doesn't really provide that much value. I feel you could cut a few paragraphs with a simpler method of explanation with perhaps more code (and explain through comments), visuals (ascii diagrams of the scopes), or something.

I also feel the analogy towards lifetimes relating to segment of lines of code might confuse users.

> Rust knows how to connect the scopes of variables that are pointing to the same thing, as well as how to know the scope of things that are more than just stack-allocated memory.

Too many 'things', knowing Rust I obviously know what you're trying to say but I could imagine it being hard to grok for new comers. However, I don't really have a suggestion on how to explain it better.

After that, you show some code and try to explain it in paragraph form after that. I think it might be easier to just place those explanations inline with the code. That way you can skip the "at line 7, it does this" type of thing.

Referencing the implementation details of the stack and heap, it seems like too many details that are not really needed at that time. Perhaps a difference between the stack vs heap, but which memory address a variable is located doesn't really convey anything useful.

Anyways, I like the where things are headed, but as you said, writing is hard. I've tried writing introductions to lifetimes and ownership and, well, I just gave up. Trying to convey that stuff after you've learned it to people who haven't is hard.

What about two intros? One for the low levelers, one for the high levelers? It's extra work, but it allows to treat more appropriately what each side needs to learn.

As [1] says: "Rust for C programmers != Rust for functional programmers."

[1] http://science.raphael.poss.name/rust-for-functional-program...

Also very possible. It's something I've been considering for a long time.
Writing is very hard. Despite the draft's "weaknesses", I thought it was well-written.

Being one who has "enjoyed" numerous encounters with C pointers (and pointers to pointers, etc.), a lot of the explanation seemed elementary indeed. But I didn't mind the "review", in fact, kind of fun imagining I was a beginner and how amazing it would be to learn the details.

I can see why Rust's concept of ownership is essential to grasp. However, the tutorial was a little murky when it came to the deeper subjects of "mutable borrow" and the cause of the example error.

It took a few readings to get what made the line "let x = &v[0];" so crucial: "...&mut is a promise that there are no other references...", i.e., meaning the promise to "x", since "x" is the recipient (owner?) of the "&mut" of "v".

The final paragraph is the key to the entire exposition, so it would be enormously helpful to take a few extra lines of text to clarify just how the expression "v.push("B");" violates the promise to "x". That's even more vital to your purpose as describing pointers in vivid detail.

People new to Rust are beginners re: ownership, borrowing, et.al., and would benefit from a step-by-step walk through of these less familiar parts.

+1; this feels unhelpfully verbose, and it tries to do too much.

I spent the last two weeks reading the entirety of the 30 minute intro, TRPL, and Referecne, and Rust By Example. My background is primarily Python / Java / JavaScript. I can read but am uncomfortable writing C.

This article touches on mutability, references, stack versus heap, ownership, and lifetimes. It has listings of C and C++ code interspersed with Rust. At one point there are nine tables representing memory layout, with no clear indication of what's changed, and only the first 3 tables even fit inside my browser in their entirety.

I understand what you're trying to get to, but I don't think this is that. I'd appreciate detailed sidebars on how Rust avoids dangling pointers / data races / etc., but not part of the main path immediately following "Hello, world." Systems language folks will readily grok that stuff without exposition on what a stack and heap are, while those of us from higher level languages would get stopped in our tracks and completely bogged down in that morass.

Lifetimes and ownership were the most difficult concepts for me to grok, but I was able to make it through TRPL relatively quickly and come out knowing exactly where I was unclear. Rust By Example helped immensely on that front. I specifically appreciated the destroy_box function on http://rustbyexample.com/move.html, though the entire snippet was quite illuminating.

The examples of lifetime elision on http://doc.rust-lang.org/book/ownership.html#examples coupled with the attempts as manually annotating lifetimes in http://rustbyexample.com/lifetime/fn.html finally made that click.

I'm just one learner, but in my experience TRPL is fundamentally sound. I wouldn't fix what isn't broken. It may not sell Rust in the sense of putting its "best foot forward," but it's concise and it works. When shooting for first impressions, it might be worth focusing on the 30 minute intro or some other document instead. I didn't touch TRPL until I had been sufficiently sold on the language from other venues to commit to learning it.

Edit: But holy shit, Steve, thank you so much for the work and care you're putting into this. Rust wouldn't be where it is without you.

> but it's concise and it works. When shooting for first impressions, it might be worth focusing on the 30 minute intro or some other document instead. I didn't touch TRPL until I had been sufficiently sold on the language from other venues to commit to learning it.

Thanks, this was literally my hypothesis for the intro, like qualifying a lead: give enough to get you interested, then either give you more or say good day, depending. Maybe this is just better as the intro itself, or many this draft is at the wrong level of abstraction, we'll see.

Not quite a TLDR, but this is a nice presentation by Lars Bergstrom that assumes more background knowledge:

https://vimeo.com/120512790

I didn't find this video helpful at all. You have to skip 10 minutes ahead to get to the actual talk. The talk is stunted and, as always with Rust documentation, dwells only on the basics. Audio isn't clear, but neither is the speaking.

I don't know of a succinct overview of Rust for experienced system programmers, and this video certainly isn't that.

A very short TLDR, in my limited opinion, is basically "Take an ML-ish, make sure everything has C-like performance, while prohibiting all memory safety issues and eliminating aliasing". From those basic principles things you can start reasoning a lot about what Rust must do.

But I agree it does feel a bit weird to need to explain pointers while also assuming people understand memory layouts. There should probably be a quick guide to memory usage, maybe even using C, and from there introduce Rust's concepts on top. It's certainly a hard problem to introduce a high-level, functional, language that also has very powerful low-level concepts.

Thanks so much Steve for doing this work. Reading the guide made it all click into focus for me when I started, and the rest is _mostly_ syntax.

Note that Rust doesn't eliminate aliasing, it merely tracks it very precisely. Taking a reference is a trivial way to create an alias, though obviously while the alias is alive you are restricted in what you can do to the referent.
How about "eliminating problems caused by aliasing", which I suppose is a subset of memory safety.
Aliasing is only a problem because they prevent you assuming two values are independent of each other: a write to one could affect the other, if they are aliased.

When you enforce immutability of aliases, as Rust does, aliasing stops being a special case. The underlying value may be the same, but the behaviour is identical to when they are independent.

The only time you have problems thereafter is when you do weird things with the value's address which only coincidentally works. For example, comparing interned strings or Java's Integer objects using == works for other interned strings and small integers, respectively.

Steve, great work! I know how hard it is to write so clearly.

The only quibble is that I'd make the middle slog a little bit shorter, maybe laying out the vectors horizontally instead of vertically, so people wouldn't forget what the text was about by the time they get to the end of the diagram.

Thanks!

Yeah, the layout could be much better, and it'd be neat if I could use real diagrams with arrows as the pointers, maybe.

I thought this intro started really clearly, but I did get bogged down a bit matching up the pointers as numbers. So yeah, I think diagrams with arrows would be a lot easier to follow. I remember seeing a talk from Niko with animated box/arrow diagrams that changed as a piece of code executed (alongside a code snippet with the relevant code highlighted). That was super slick, and I thought it really helped make things clear.

Also, is it standard to talk about memory growing "down" as the memory locations numerically increase? That's the opposite of my impulse (I would have said that 0xff is the top of memory, and 0xfe is "down" from there) but TBH it's been a while since I took the relevant CS class.

I'm a Python user, and always dipping my toes in other tech, working through books on Go, wrote some Node servers and so forth. I rarely find something that has enough advantages to warrant augmenting or replace Python or bring it into 'my stack'. The time involved to really master these things isn't worth it, in my opinion.

Something with no GC for hard real-time systems would fit the bill, also something that I could write performance-critical libraries with a C ABI like Rust to call from Python. I've had my eye on Rust and of course C(++), but mostly waiting for Rust to stabilize before diving in.

Guides like this are going to mean a lot in the coming months for people like me who are Rust-curious but don't have a systems programming background.

I'm not so much looking for yet-another-scripting language or GC'd language but a systems language that perfectly augments my Python/SQL knowledge.

Thanks for doing this, Steve.

Awesome, thanks. I think you exemplify something that's gonna happen a lot in the next year or two, and I'm really interested to see how it goes. I learned systems stuff in college, and so when people have asked me for resources, I'm a bit lost... So my solution is to make them. I'm hoping others do too. Systems is fun!
You should consider the Nim language. It "feels" like Python but has seemless integration with C, and native performance like C.

http://nim-lang.org

I did at one time. I've been keeping my eye on Nim (though a far less watchful one than on Rust) to see if the standard library works one day without using the GC.
It's still too confusing. Try drawing actual pictures to illustrate the scope/lifetime issues. Talking about addresses on the stack is an implementation detail.

Things to cover:

Basic lifetime concepts in Rust:

   1.  Scopes are nested.
   2.  Variable lifetimes are tied to scopes.
   3.  Unlike other languages, variables can be created
       tied to a scope outer to the one where they are
       created. The user can control this by indicating
       that the lifetime of a variable is the same as that
       of some variable from an outer scope. This is Rust's 
       big innovation.
   4.  A function can create a new object and return it by
       reference if the function explicitly says that the 
       new object has the same lifetime as some parameter.
Basic ownership concepts in Rust:

   1.  There are three kinds of ownership - single ownership, 
       reference count multiple ownership, and thread-safe reference 
       count ownership.
   2.  The reference count ones work like you'd expect.
   3.  Single ownership interacts with the lifetime system in 
       to make it possible to create data structures such as
       trees, with all components single ownership and having the
       same lifetime.
   4.  The "=" operator usually means "move", and when it does,
       the right hand side of the "=" becomes inaccessable.
       (This is type-dependent; some types copy when "=" is used.)
       This preserves single ownership.
Basic data structure concepts in Rust:

   1.  Rust has enums, which, unlike enums in other languages, can
       have data objects associated with them. They're really variant
       records.
   2.  You can declare an enum with a data object as a field in a 
       structure. This creates a structure that contains (really links 
       to) another data object.
   3.  This can be done recursively, so you can create trees of
       structures.  
   4.  You can't violate the rules of single-ownership.  Trees are OK;
       directed acyclic graphs or backpointers are not allowed using
       single ownership.
   5.  There's no "Null". Instead you create enums which have one value
       with an associated data item and one without. 
   6.  Everything has to be initialized to a valid value for its type.
Basic borrowing concepts in Rust:

   1.  You can create temporary references to something with an equal
       or longer lifetime, because the temporary "borrowed" reference
       cannot outlive the thing it is referencing. That's safe.
   2.  You can have one writer (a mutable reference) or N readers 
       (immutable references) at a time.
Basic type concepts in Rust:

   1.  It's complicated.
   2.  There's automatic type inference.  Not just for "let", either.
   3.  Function parameters have to be declared, not inferred,
       except for the special syntax for closures that lets you write
       concise lambda expressions.  This limits how hard the
       type inference system has to work.
   4.  I don't fully understand Rust's type system yet.
Basic safety concepts in Rust:

   1.  All of the above is enforced at compile time.
   2.  The result is memory safety.
Misc. notes:

  - Lifetimes are novel, but not that complex.
  - The type system really is complex.
  - There are generics and you'll need them more often than in C++.
  - The compiler first checks syntax, then types, then borrowing. It does not report
  borrow errors until everything else is correct.
  - There are no exceptions, and proper error handling requires rather verbose code.
  - Have a plan for your variable lifetimes before you start coding, or you end up
  fighting with the borrow checker and losing. 
  - If you think you need "unsafe" in pure Rust code, you're doing it wrong.

   3.  Unlike other languages, variables can be created
       tied to a scope outer to the one where they are
       created. The user can control this by indicating
       that the lifetime of a variable is the same as that
       of some variable from an outer scope. This is Rust's 
       big innovation.
This isn't quite right (or I'm not understanding what you're trying to say).

Given

   let x = 1;
   {
       let y = 1;
   }
there's no way you can make `y` itself live as long as `x`.

   4.  The "=" operator usually means "move", and when it does,
       the right hand side of the "=" becomes inaccessable.
       (This is type-dependent; some types copy when "=" is used.)
       This preserves single ownership.
I think this is "complicating" how =/moves work: it's really binding things by-value that are moves, the = operator isn't special. E.g. `foo(x)` will move `x` into the `foo` call, despite there being no `=` at all.

   2.  You can declare an enum with a data object as a field in a 
       structure. This creates a structure that contains (really links 
       to) another data object.
I don't understand this, could you clarify?

  - The type system really is complex.
I don't think this is very true, what complexity do you see?
I'd answer this, but I haven't used Rust for three weeks, and the language changed again. None of my programs will compile any more. (Sorry, I'm in a cranky mood about Rust right now. I just had to file an issue on the Hyper HTTP library because some names were just changed in the I/O library, making the HTTP library uncompilable.)
Regarding the first point, about scope, I'd guess he's referring to lifetimes and how you can explicitly annotate that something can live as long as another thing, like a parameter. Not scope exactly.
Right. When you give an object A a lifetime that makes it live as long as object B, you're effectively putting object A into the same scope, for memory allocation purposes, as object B. This works even if B is in an outer scope. This is a new, and very powerful concept.

Any tutorial on this needs to be illustrated. Talking about line numbers is not enough.

This... isn't how lifetimes work.

As I said above, there's no lifetime annotations you can use to ensure that y lives as long(aka is in the same scope) as x in the following

  let x = 1;
  {
      let y = 2;
  }
This is also true if you do dynamic allocations: there's fundamentally no way to "promote" variables to a higher scopes just by using lifetime annotations.

Lifetimes are a purely passive thing, designed to allow the compiler to check the programmer's use of references, not to allow the compiler to massage things around to ensure that whatever use work.

That is, if a Rust program compiles, then it will also compile (modulo needing extra `unsafe` annotation) and, importantly, do the same thing, if all the references are replaced with raw pointers, which don't have lifetimes to describe their relationships.

You're right. I thought you could use lifetime annotation to force something to be allocated in an outer scope. But Rust doesn't allow that.
I sounded to me like Animats was describing an "active lifetime" system, where the compiler will change where variables are placed, when they are deallocated (etc.) to make whatever code work. Rust's lifetimes do not work like that.
I think traits are really confusing (dispatch by input AND output type), there's also trait inheritance, trait bounds, casting things to traits; add type inference to that, lifetime parameters and generics. You get things like

    struct Foo<'a, P> 
        where P: std::ops::Neg + 'a,
           <P as std::ops::Neg>::Output: 'a {
        foo: &'a Bar<P>,
    }
after further research this makes sense, of course, but looks like non-sense to anyone just trying to learn the language
Hm, maybe I just "know too much", but it seems to me that most of the parts of the type system are relatively independent: there's not too many weird interactions between the various parts, and many systems are complex because of the surprising interactions.

In any case, half the "complexity" in the example you give is the noise of using the fully-qualified std::ops::Neg path in the bounds, rather than importing it and just writing `Neg`. :)

TBH whenever I see type names being added to each other, my brain kind of shuts down the relevant lobes. Granted, I'm barely initiated to Rust, but... it just looks confusing. (It doesn't help that it's not actually type names, but a type name and a lifetime...)
I've seen this example several times now, and I never got why it was so bad: it's mostly an objection to the namespace qualification syntax if anything. It would actually be written:

    use std::ops::Neg;
    struct Foo<'a,P> where P: Neg + 'a, <P::Neg>::Output: 'a {
        foo: &'a Bar<P>,
    }
And with further proposed improvements it would be:

    use std::ops::Neg;
    struct Foo<'a,P> where P: Neg + 'a, P::Output: 'a {
        foo: &'a Bar<P>,
    }
Not bad. It's saying "I have a generic structure over a type parameter P tied to a lifetime 'a, and P must overload the unary minus operator".
I think namespace qualification syntax and trait bounds make it more confusing

`<P::Neg>::Output: 'a` gives me eye cancer and my intuition is if `P: Neg + 'a` then `P::Output: 'a` is implied (why would it not have that bound if P does?)

the Neg trait's Output IS Self (as Neg)! Why is the bound not transitive?

> `<P::Neg>::Output: 'a` gives me eye cancer and my intuition is if `P: Neg + 'a` then `P::Output: 'a` is implied (why would it not have that bound if P does?)

What if the output of the unary minus method was say, a constant 'static? This is a reasonable thing for a function to want to do in general. (Probably not for Neg in particular, but obviously we don't want to add special cases in the inference for specific traits.) The function needs to know about it because you can do many more things with a 'static than a 'a (as 'static references can never become dangling).

But it's not, the Output is Self not Self + 'static
I think you're misunderstanding what Self means in the definition of Neg. The Self is only there so that it is clear which type's "Output" is being referred to. It doesn't establish any type/trait/lifetime relationship between the type of the receiver and the output type. It would be perfectly possible to implement Neg with a 'static output lifetime.
Yeah, I got it, people on IRC helped me understand that it's not casting it as `Neg`, it's casting the type as `Neg` for namespacing purposes
Thanks, this explanation is about 10-100 times better than the one in the submitted article.
it's probably because it's partly wrong and/or doesn't explain the complicated stuff.
I really like your writing style - it's a little verbose in places. The paragraphs detailing what each line number does would be easier just as pictures / diagrams with arrows to the source code sections. Likewise quite a lot of this could be made mode succinct, which would aid readability.

Along with that, it's really hard for me to get used to right-hand line numbers. Shoving them along the left hand side like most editors would be much much much easier to read for me, at least.

That said, I do enjoy your writing and find it quite readable.

Thanks! It is a bit verbose, that's what I was going with with this version. You have to explore the extremes before finding a comfortable medium, you know?

I do agree better diagrams would help, for sure. And yeah, the right hand numbers were kind of a compromise: they kept the exercises compileable without needing to mess with markup stuff, which is just a weakness of markdown :/

What confused me was that the piece just seemed to stop, without a proper ending. It told me that "Rust stops you doing X", but not "And here's how to do it correctly in Rust."
That was great, I was completely unfamiliar with Rust's borrowing system and now I think I understand it!
I like the new structure much better!

Now, I'm in no way a writer. English isn't even my main language.. So take this next point accordingly:

Unfortunately, I get an even more condescending vibe from this draft than I got from the book. Not as in "I'm an expert and you're treating me like a newbie," but as if I was a kid instead of an aspiring Rust programmer (beginner or otherwise). I wish I could point out -exactly why- I get this feeling, but I'd be lying.

I do think I'm starting to see a pattern, though, that perhaps contribute to this gut reaction. The pace between concept introductions is odd, somewhat erratic. At times, in one line you're talking to a complete novice programmer, and the very next you assume they know other languages and general concepts [1]. Other times, you take some basic knowledge for granted and explains it right after [2]. I know it must be very hard to find the right balance for your target audience, but I found these jumps very jarring. For what it's worth, once you get past the introductions you pick up a more to-the-point-explanations rhythm and that reaction goes away.

[1]: For example, after several paragraphs and two code snippets to introduce the concept of 'variable bindings' in a novice-friendly way, even going as far as (somewhat) explaining what system and functional languages are, the text suddenly assumes I know about program execution and what a control flow structure is?

> As you know, a computer program is executed line by line. At least, until you hit a control flow structure, anyway.

This line was specially off-putting. After all that explanation, the assumption I'd know this made me feel like a kid. But I can only imagine how inaproppriate I'd feel if I didn't know. Given the novice level of the text up to then, the 'as you know' sounds very out of place.

[2]: The very next paragraph, we go over a program with one variable binding line by line. And then, it's assumed I know about scopes. But after that they are explained anyway. I realize this is a way to go over every concept, even if one already knows them, but it feels odd! Both beginner and experts would understand a plain explanation. But this back-and-forth about how to present concepts doesn't work -- for me. It'd make more sense to read straight-forward explanations of what I already know than these "as a programmer, you know that x," mixed with even simpler concepts being introduced as new.

This sounds harsher than I planned -- I'm no writer afterall! But, really, despite these gut reactions, the Rust book is turning into one of my favorites. Thank you for all your work!

> I wish I could point out -exactly why- I get this feeling, but I'd be lying.

It's hard, for sure. There are certain sentence structures that strike certain people this way, and my style sometimes triggers them. For example, "basically" has no negative connotation for me, but really gets some people going, so I've been eliminating it wherever possible. I wonder which aspect does it for you... thanks for the brain dump, this is helpful.

I was actually reading the rust lang book for the first time yesterday, specifically the intro to lifetimes that this post is a revision of.

I'm not sure jumping so much into what's going on in the C++ side is a great idea. The section as it stands now covers the important points (with out lifetimes invalidating pointers is easy) without getting too lost. However, I'm already pretty familiar with C++, so the more brief explanation may not be enough for everyone.

I suggest you go with prose that clearer over prose that is more stylistic. For example:

Line six has a closing curly brace, and so main, and thus, our program, is over.

Could be rewritten as:

Line six has a closing curly brace, and so main is over. By extension, our program is over as well.

Maybe even with a quick reinforcing of the (hopefully earlier explained) concept that main is the main program block.

It's a fine line to walk, making the text interesting while also making it very clear, but I think there's something to be said for making the English easy to parse, since the people going through the tutorial will have enough on their hands dealing with the code examples.

I agree. Dedicated tutorial authors of languages with advanced concepts such as Rust, Haskell, etc. really need to take a course or have a quick overview of technical writing. Steve does a pretty good job with Rust, I think. I wish Haskell had good tutorial authors like Steve. Haskell really needs better tutorial authors.

Are CS majors typically required to take a technical writing class like other engineering disciplines?

In Portugal no.

We also don't distinguish between CS and Software Engineering degree.

I had to take one. All I remember from it is making posters and graphs. We didn't actually learn BETTER WRITING in it, but how to write RFCs.
I would move that part up by the first time the code is shown, replacing "This small snippit is enough to start with." with some more concrete description of the program, like "The smallest rust program that will actually do anything." (I'm not sure that is exactly true, just illustrating my meaning).

I would then go further than you suggest, saying something like, "The blank lines, 1,3 and 7, do not have any impact on the program." Then "Line 4 binds the value 5 to the variable x.", followed by the explanation of what that is called in rust.

After that, "Lines 2 and 6 are the beginning and end of the program. For this program, they also define the scope that owns 'x'." Followed by the explanation of ownership.

Once 'let' is established, a short explanation of 'mut' can follow.

In what sense this introduction is alternative? It's just like every other intro that explains basic and easily understood things about the borrow checker, but doesn't go into details on how you actually live and work with the damned thing. The second thing you do after learning basics is trying to implement some simple data structures, like lists, or even (horror!) double-linked lists. In Rust this is the moment where you start hitting roadblocks, and then you read somewhere that implementing "basic" data structures in Rust isn't a basic exercise at all, and that is kind of discouraging.
This seems like an unfortunate pathological case caused by the abstraction level Rust sits at. The kinds of tree- and graph-like data structures that garbage collected languages can represent in safe code (at the cost of performance) don't work as well in Rust; you can still implement them with various mechanisms (std::swap is very important for trees; Rc and Weak; replacing pointers with indices into a global Vec; etc.), but due to a desire to get optimal low-level performance, core data structures often end up using unsafe blocks in their implementations. The thing is, though, implementing data structures is something of a worst case: in most other code you can use the data structures to provide the pointer relationships you need, but when implementing them you have to do that yourself. Most other code is more naturally amenable to working with the borrow checker - which is not to say it's not difficult.
This isn't unique to Rust, in fact, as modern C++ presents the same dilemma. Here [1] is a Stack Overflow question where someone asks how to make a doubly-linked list out of smart pointers, and all the answers either involve shared_ptr or C pointers. Same pedagogical issue: you want to deemphasize the less-safe pointers (in Rust, the ones behind an "unsafe" gate; in C++, the raw C pointers) in favor of the safer abstractions, but you hit a wall when it comes to doubly-linked lists.

C++ has it easier in practical terms, I guess—few projects actually use smart pointers as pervasively as modern C++ guidelines stipulate (and they're hard-wired into the language in a couple places like operator new and this), so C pointers usually have to be taught pretty early anyway. In Rust, by contrast, unsafe pointers are considered evil things you touch only when you know you have to. But the problem is still there: it feels like something that comes with the territory of manual memory management in general.

[1]: http://stackoverflow.com/questions/15384443/doubly-linked-li...

I don't get why they just don't suggest using weak_ptr<>() for the backwards reference.
You need to use weak pointers for backpointers in Rust, because there's no GC. If you create a cycle with reference counted objects, objects are never released and the program will leak memory.
Of course, but my question was why it wasn't explained, I know very well the reason why weak pointers are required.
I guess this would solve the problem but it would also be slow because traversing the list via weak_ptr requires a conversion from weak_ptr to shared_ptr for every item.
There isn't any choice in RC systems in presence of cycles, either weak pointers or a presence of cycle collector.

Anything else is unsafe, specially if the pointer is allowed to escape the RC object that contains it.

And this is one of the reasons why usually optimized GCs outperform RC solutions.

Unless approaches like affine types or counting elision are used, but those fail down in the presence of cycles, forcing the developers to use other approaches to represent cycles.

There's little benefit to doing so. Because weak_ptr points to a shared_ptr, you are still paying the overhead of the reference counting either way. You might as well just use shared_ptr and have a destructor that unlinks every element from the list.

weak_ptr is basically for managing leaks, not for avoiding reference count overhead. Leaks aren't the problem in a doubly-linked list, however.

Thanks for jumping in. Yeah I guess it makes sense.

It just got me thinking into having some kind of data structure examples in Rust as side project, after 1.0 availability.

Now just have to find time for it.

> In what sense this introduction is alternative?

Well, I wrote our existing introduction, this is a different version, that takes a fundamentally different approach: small example, high level of detail, rather than larger examples at a higher level of detail.

Your point about it being an introduction stands, but that's why it's an alternate _introduction_.

I agree with some of the caveats listed by other people here (in general, that this guide seems wishy-washy about what type of programmer it's catering to), but I like the memory layout tables. While I don't remember much about how I thought at the time I might have benefited from such a tutorial myself, when these days I mess around with exploits and the like which do weird things to memory, it often feels surprisingly grounding and illuminating to open up a debugger and start printing the contents of memory, even if on a theoretical basis I already know what it's supposed to look like.

But please make it clear that the tables don't accurately describe the real layout of memory in a compiled Rust program. Heap mappings and thread stacks are typically at random (thanks to ASLR) addresses, rather than the start and end of memory, and the locations of specific heap allocations are usually based on complicated rules depending on the size class of the allocation (differently sized objects never get allocated together in most allocators), thread caches, ordering of buckets within a page, etc. (Some architectures also grow the stack the other way.)

Yes, it's always about the right level of abstraction. Making it clear that it's not _actually_ 0x00 would be good, but then you can get into discussions about virtual memory, and all that other stuff that you're talking about, which just muddies the point of the example. It's a fine line.
Steve, really great work, I've appreciated all your efforts as I've played around with Rust as it approaches 1.0. I think this kind of really deep treatment of an important topic belongs somewhere in the introductory docs. I especially like that you give concrete examples of the problems that Rust's concept of ownership solves. The memory diagrams are actually quite helpful (IMHO) regardless of the "direction". Keep up the great work!
I like it, but I would make two suggestions

1) There are many memory schemas for the push_back example, maybe you want to organize them side-by-side with an arrow between them showing which statement caused the change. Saving some vertical space may do some good.

2) I'm not a fan of the sentence structure in many places. Needs more pith. Perhaps apply some Strunk & White and some K&R.

I wonder, for people coming from non-systems programming languages (e.g. Python), whether it would help to visualize what is actually happening under the hood in a scripting language and comparing that to Rust. As others have noted, this guide is written as if to C/C++ programmers, while the intention (I had thought) was to also target scripting language folks?

My experience, having started in C++, then gone to scripting languages, then come back again, is that it can be quite helpful to consider what actually happens under the hood in a scripting language. Because Python's references are more limited, you would never exhibit the same bug that you illustrated in C++, but there are still issues around e.g. aliasing which tend to trip up inexperienced Python programmers, and where you could illustrate interesting things about Rust's ownership model. And boxed/unboxed data types plus reference counting (in Python) would give you an excuse to talk about RAII. So I think that you could do this in a way that would actually speak to savvy scripting programmers, while still being definitively systemsy.

Just a thought.

That example of aliasing - is it possible to write such thing in Go? Will Go compiler eat it or will not compile? Just interesting, how Go behaves in comparison to Rust, as another modern language.
Go uses garbage collection, not ownership and borrowing.
You can have aliasing pointers in go; see https://gobyexample.com/mutexes for how you have to explicitly lock to update the target without undefined behaviour.

However, since it's a GC, it's probably not possible to have a pointer that refers to a memory location that doesn't exist, because having a pointer to a memory location is by definition what keeps it from being free'd by the GC.

thanks. but time between 2 lines of code can be not enough for GC to be called to free first address. Maybe they just control it on language level.
I liked the structure of the guide. However pairing the memory tables while mentally executing the code line-by-line got confusing for me. (Especially because the code was offscreen because the tables take up vertical space)

I'd love to see this kind of explanation done in a non-text medium. For example, a slideshow or interactive webpage (ex: http://www.redblobgames.com/pathfinding/a-star/introduction....)

I really wish I was better with front-end tech, creating those kinds of things would be awesome, but above my skill level, unfortunately :/
> Rust’s variable bindings are immutable by default, but can become mutable, allowing them to be re-bound to something else

From someone reading about these details for the first time:

Calling them variable bindings confuses the matter. You say, just moments earlier, that variables are called by that name because they can change over time, they’re mutable. But this variable is immutable? But only sometimes? !

I know you mean that the mutability varies and that it makes sense in the context of the jargon of functional languages, but that's something that will make sense to language developers who have worked on Rust for years and maybe to the HN crowd, and not to new coders trying to grasp difficult, novel (to them) concepts.

How about "multimode bindings" or "multiphase bindings" (as in liquid/solid phase transitions) or dual-mode or something more entertaining like "double-agent bindings". Is it too late to change?

Maybe I'm missing something, but I suspect years from now people will be saying, 'so variable bindings are not always variable? huh?'

'Variable' is a jargon-y term in mathematics/programming in general, it doesn't literally mean "this thing can vary in the run of this program", e.g. even Haskell calls its name bindings "variables", and it is focused on immutability much more than Rust (I suppose this is the functional programming aspect you were mentioning).

One way to view it is that the bindings don't have a value that is fixed at compile time: they vary between runs.

In any case, I'm not sure what you mean by "mutability varies".

"Variable" means that the value within can vary, as opposed to a constant, where the value never varies. This has nothing to do with mutation. For instance, a function's arguments are always variables as the value within each argument can vary each time the function is invoked, and they are variables regardless of whether or not the function ever mutates them.
Or just drop variable completely and use just "bindings"? Perhaps with a note that this is similar to variables in other languages.
When faced with similar confusion, I used "bound value" instead of "variable". I thought it might be better.
> You highlight lines four three six.

I guess there is a typo and should be "four through six" instead.

So basically, anyone who doesn't already really appreciate Rust must not know what a stack is, what a pointer is, or what immutability is?

I'm sorry, but this implication enrages me.

If I'm just trying to print out "Hello World", why would I use some kind of list and a pointer?

You touched on an example of where Rust simplifies non-GC memory management.

Its nice that there are various ways to track when you are done with pointers (and memory referenced by them) across the code.

I think some of the techniques are very useful. But its taken to an extreme in Rust where you will do anything to avoid GC.

Some of that stuff involving tracking and matching ownership or correspondence across the code would work better outside of a purely sort of one-dimensional textual representation. This is an example that illustrates why textual source code is a legacy.

But it IS text, and often, with Rust, its too complicated, and often not easily applied to typical "systems" programming (which is quite often used to actually mean some ordinary types of user applications, or programs that do the same thing).

Personally I think Rust could maintain a good portion of its core strength while losing about half of its complexity.

Also, in terms of an introduction it would be nice to see some real useful "systems programming" examples where the core memory tracking and other capabilities make Rust stand out. Hello World isn't a good example of this.

We want the compiler to be smart and be able to avoid collecting things at runtime when possible and practical, and for the programmer to be able to effectively indicate corresponding data so the compiler will know when it should be deallocated. However, we also need the code to be clear and concise and to avoid making the programmer do routine work when its not necessary.

   > while avoiding about half of its complexity.
If you know how to do this, while staying within Rust's design goals, we'd love to hear about it. I think Rust is no more complex than the problem demands, but I'm biased!
> Personally I think Rust could maintain a good portion of its core strength while losing about half of its complexity.

How?

Like it, but contains some typos.

becuase

langauges

ownerhsip

Like the writing style and the detailed play-by-play traces. A small tweak that would help to make it easier to read would be to visually highlight what changed from one memory snapshot to the next.
Hm, I too got the sense that it's not clear what the "prerequisites" are for reading the article. Parts of it were really straightforward to me "oh, I know roughly what a stack and the heap are" to confusing "wow, I don't know anything about C++". I think a few distinct guides targeting different sorts of programmers might be the answer here.

But I do like the idea of focusing on ownership and lifetimes more front and center, since that's rust's raison d'etre.

Can you get a hold of Niko's slides from the NYC meetup this week at Bloomberg? I was there, and his overview of ownership and borrowing, using the examples of a book, were actually really helpful for me. For whatever reason, his approach really resonated with me.