New to rust. Is this a good approach?

I've got some C and C++ behind me, but it's been a few years since I've used it. I can remember reading a 1000 page book on C++ (C++ for Dummies) and then asking for help, only to have members say that the book I read was not a good one. I'd rather not make that mistake again.

I'm reading this online book: https://doc.rust-lang.org/stable/book/title-page.html

The other book I'm reading is a fork of the above book and is said to offer some helpful information and quizzes: https://rust-book.cs.brown.edu/title-page.html

Currently, I'm on chapter 4 (Understanding Ownership), and so far I'm understanding everything just fine. I've decided to read one chapter of the main rust book and then read the forked book chapter to really pound it in. Then I might watch a few videos on YouTube to see if I can follow along.

I'm not trying to make any programs with it right now. I'm just reading and copying/pasting into Visual Studio Code to try it out and play around just a little to experiment.

Does this seem like a decent way to learn rust? I really don't want to waste my time if there's a better way. So far this seems to be working.

Yes, "The Book" (which you're reading) is the default recommendation.

If later you find yourself disliking The Book, I've heard from some C++ people that they prefer Programming Rust, 2nd Edition [Book] so you could try that if you want something different.

1 Like

Note that the contents of the Brown version are controversial. In particular,

  • Its quizzes that ask about Undefined Behavior are written to assume a simplified model that is dangerously incorrect if you ever want to write unsafe code. If you give correct answers (insofar as there are any correct answers to “what if the compiler accepted this program that it does not?”), it will tell you you are wrong and there is no undefined behavior.
  • Many people find the detailed "permissions" discussion more confusing than helpful.
3 Likes

Rust is both too early to have much in the way of bandwagon jumping trashy books, and to have fully complete and simple references.

The official resources on the Rust site are pretty good, but they won't cover everything you'll run into, and can lag a bit behind the language. The section on async is careful to avoid talking about async lambdas, for example, since they weren't directly supported until recently and the workarounds were a bit messy.

Also, as a reference they can't really give great suggestions on what libraries you should use most of the time, and there's quite a few you should have in your toolbox. The error handling section doesn't mention using libraries like anyhow or thiserror and why you would or wouldn't, because maybe tomorrow they get superceded by some much better approach (it's happened before!). lib.rs, the unofficial mirror of the public crates registry, has some really handy categories that really help discovering useful libraries.

Certainly, if something feels weird and messy as you're learning, asking here is always welcome! You're going to get confused in Rust, especially for a new language, and finding out you're barking up the wrong tree early can save you a lot of grief.

2 Likes

Reading a bad book is indeed a sad experience. Generally I think that an adult person should notice early if a book is bad for them. For children that might be different, and when looking at all the 5 star reviews at Amazon, I indeed get the feeling that some people just give each book they buy just 5 stars before reading. For C++ I felt in a similar trap some decades ago. I bought "Thinking in C++" because it had good reviews, and was also freely available as a PDF. But when reading, I discovered early that is was too verbose and too much praised OOP design, as it was based on a Java book. Some years later, when I again tried to learn some C++, I discovered two equally named books at Amazon, both with good reviews. But before buying, I discovered in a C++ forum, that only one of them was really liked by experts, so I finally read that one, and it was quite good.

For your Rust learning journey, I would suggest that you select one of the official tutorials -- reading both is wasting your time, as both are quite verbose, and content differs only for a few chapters. "Programming Rust" by Jim Blandy should get a third edition soon, you can read that after the official tutorial, and then maybe, for some background information, "Rust for Rusteans" by Glengset.

If you actually remember some C, and prefer a more compact introduction, you could also consider starting with my "Rust for C-Programmers". But as you are already at chapter 4 of the official tutorial, I suggest to continue with that. And perhaps try early some exercises, I think one is called Rustlings, but there are more.

1 Like

Thanks. It seems that I don't remember enough C code for that book to be helpful, but if I did, I'm sure it would be.

Thanks for the comments on the Brown book version. I do find it helpful in some ways, so I'm going to continue reading it until it becomes confusing or I don't get value from it. If that happens, I'll just go over the main rust book twice. There's no harm in going through it twice if it's good info and helps me to remember.

Thanks for this info on libraries and error handling. This is good to keep in mind. Rust is relatively new, so I expect there to be some bumps in the road for a while.

The Programming Rust book by Jim Blandy book looks interesting, so I'll keep it in mind for when I finish the main online tutorial. The Rust for C Programmers is too much for me as I don't remember much of the C code.

I can say though, if I hadn't done all that reading on C and C++ about pointers and references and vectors and arrays..., there's no way I'd been in as comfortable a position right now. I'd very likely be lost

It's a decent way, overall. For the ownership / borrowing sections specifically though, I'll throw out some heads-ups.


Almost all Rust learning material presents a "mutable vs immutable" world view, at least in the beginning. A more accurate dichotomy is "exclusive vs shared". &mut _s must be exclusive, even if you don't actually mutate something. And once you get to chapter 15, the book will introduce types that facilitate shared ownership[1] and shared mutation[2] -- the ability to mutate through a shared reference (&_).

You don't have to master these immediately, but it's good to know they exist up-front, so that you aren't surprised later.


In terms of how borrow-checking works, the Brown book presents a more accurate mental model than the official book. Rust lifetimes -- those '_ things -- are about the duration of borrows, and not directly about the liveness of values. They are not based on lexical blocks, though this is commonly used as an illustration. The compiler uses lifetimes to statically analyze when and how[3] variables and other places are borrowed. It then checks every use of every place and makes sure there is no conflict with being borrowed.

The Brown book presents this as sort of permission system.[4] It's probably a lot to take in at first, but it's also a lot more accurate that the official book. They also cover concepts like reborrowing which the official book does not.

There's probably a better approach some place between how the two books do it.

A few examples about borrow-returning functions

Here I'll talk about function signatures where a borrow in the input is also present in the output. (The bodies aren't important for what I'll be emphasizing.) This is a borrow-checking topic which is talked about a bit later in the Book (chapter 10).

You could perhaps skip this or come back to it after reading Chapter 10, it may be a bit to targeted as a reply to your general question.

//fn ex1<'a>(slice: &'a [String]) -> Option<&'a str> {
fn ex1(slice: &[String]) -> Option<&str> {
    slice.get(0).map(|s| &**s)
}

//fn ex2<'a>(slice: &'a mut [String]) -> Option<&'a mut String> {
fn ex2(slice: &mut [String]) -> Option<&mut String> {
    slice.get_mut(0)
}

//fn ex3<'a, S: ToString>(vec: &'a mut Vec<String>, s: S) -> &'a str {
fn ex3<S: ToString>(vec: &mut Vec<String>, s: S) -> &str {
    vec.push(s.to_string());
    vec.last().unwrap()
}

fn ex4<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1 < s2 { s2 } else { s1 }
}

Recall that Rust lifetimes represent the duration of borrows (not the liveness of values).

The meaning of ex1 and ex2 is that the slice (the [String]) remains borrowed so long as the returned value is in use. With ex1 the borrow is shared; with ex2 it's exclusive. This is enforced at the call site, no matter what the bodies of the functions are.[5]

With ex3, the Vec<String> remains exclusively borrowed while the return value is in use. The exclusive borrow of the input does not "downgrade" even though the return type is a shared borrow. That kind of API would sometimes be nice, but Rust doesn't have it yet.[6]

With ex4, both str from the inputs remain borrowed so long as the return value is in use. The use of the return value determines the duration of the borrows (the 'a lifetime) at the call site.

This last example corresponds to an example in Chapter 10 of the official book. Chapter 10 talks about borrow checking[7] and lifetime annotations. Its overview of how borrow checking works is quite inaccurate; it talks about the liveness of input parameters a lot, but again, Rust lifetimes are about borrow durations.

The way the borrow checking of ex4 works -- longest in the book -- is pretty much exactly the opposite of what they say. The borrow checker does not look at the lifetimes of the references passed in a choose the shorter for the return value at the call site. Instead, the use of the return value determines how long the borrow of referents of the references passed in must be -- and, as per the signature, that lifetime is the same for both inputs.

Where does value liveness come in (example 10-23)? Being destructed[8] conflicts with being borrowed. The borrow checker didn't see the liveness scope and assign a lifetime based on that.[9] The borrow checker determined that string2 was still borrowed at the point it went out of scope


Both the Brown book and the official book over-emphasize "stack versus heap". But heap allocated memory is not the only motivation for the ownership system, and there are other misguiding statements as well. For example the book presents Copy as a trait for "stack-only data". But that is inaccurate in multiple ways:

  • Types don't care where their instances are stored. All their examples of "stack-only data" -- Copy types -- can be moved to the heap (e.g. put in a Vec<_>).
  • Copy types can also refer to the heap (or static memory, etc).
  • There are types that can definitely not be Copy which can also reside entirely on the stack.

In actuality, Copy is mostly about resource management. Types are only eligible to be Copy if they do not have a destructor. Types which need to deallocate some heap memory when destructed are not Copy, but that is not the only kind of resource management.

E.g. File cannot be Copy,[10] even through from Rust's perspective, it's just an integer (an identifier from the OS). And though you probably won't run into this type for some time, Ref<'_, T> is a type with a destructor[11] that can reside entirely on the stack.[12]

Much of the official ownership chapter could be rewritten with File as the example instead of String, without going into all the stack-and-heap details.[13]


Borrow-checking exists for reasons other than preventing dangling references, too. The Brown book has a related problem: they settled on some (unspecified) subset of what is undefined behavior in Rust, and then ask the user to judge if various examples would trigger UB or not in their quizes.[14] But in most cases, they are really trying to point out one specific memory safety issue which (safe) Rust makes impossible. And when I last reviewed some chapters, they had some questions where they said something was not UB when it actually was -- for reasons outside the point they were trying to make.

So my advice here is to interpret those parts of the Brown book as examples of specific, definitely-bad things that could go wrong if Rust didn't have the protections that it does have.[15] Do not take them as authoritative examples of what isn't UB in Rust. In particular, they are not suitable illustrations of how to write sound unsafe code.


Finally, if either book[16] leaves you with questions, feel welcome to bring those questions to this forum.


  1. i.e. there is not always exactly one owner ↩︎

  2. aka interior mutation ↩︎

  3. exclusive borrowed ("mut") versus shared borrowed ↩︎

  4. (despite their phrasing, this isn't exactly how things are actually implemented in the compiler) ↩︎

  5. The function signature is the contract. ↩︎

  6. The meaning of the signature that we do have cannot be changed without making existing methods unsound, so it would have to be a new kind of API. ↩︎

  7. with an overemphasis on dangling references ↩︎

  8. "dropping" ↩︎

  9. liveness scopes are not even lifetimes ↩︎

  10. the File gets closed by a destructor ↩︎

  11. it's basically a read lock ↩︎

  12. It's defined in core, where there isn't even the concept of a heap. ↩︎

  13. e.g. closing a File twice would also be UB. ↩︎

  14. Usually by imagining Rust accepted more code in an also unspecified way, i.e., by imagining some language that isn't Rust. ↩︎

  15. Probably they are examples aimed at C/C++ programmers. ↩︎

  16. or other learning resource for that matter ↩︎

5 Likes

Thanks for taking the time to write this up. I have to say that some of it went over my head as I've not covered the topics, but this will be a great once that happens, so I'll be book marking this post to refer back to.

I have to say that I didn't exactly understand what the Brown book meant by 'undefined behavior', so I basically summed it up to code that won't compile.

I'm not even sure what I want to program with rust, but I do have the time to learn it, and I thought it would be nice to be able to rummage through github and poke around with some of the rust applications for Linux.

I'll be sure to let you guys know if I have any other questions. You've all be so helpful!

You should really know that, as you told us that your learned some C++ long time ago. Classic undefined behaviour in C and C++ are uninitialized variables, pointers to random locations, or to data that is already freed. C and C++ has a lot of them, including race conditions in threaded code, e.g. results can depend on random order in which the code is executed. Luckely most higher level languages including Rust typically avoids all these hard to debug problems.

1 Like

Here's an introduction. In brief, if a program executes something that is undefined behavior,[1] it can end up doing anything. What may happen is not constrained or predictable. UB bugs are extremely insidious.

The good news is that the only way you'll encounter UB in Rust is if

  • You write unsafe code
  • You have a dependency that used unsafe incorrectly
  • The compiler has a bug

So if you stick to safe Rust, you shouldn't have to worry about it in your own code.


  1. or is deterministically going to execute something that is undefined behavior ↩︎

2 Likes

The shortest definition is code that unfortunately does compile. Technically, it's that you're giving the compiler invalid input that doesn't have a behavior defined by the language, but that the compiler doesn't prevent you from using.

In practice, this means the optimizer may do effectively anything to your code as it makes increasingly incorrect assumptions, removing checks required for security is a common notable one.

In Rust, these can only happen due to unsafe code explicitly declared as such, where in most languages all code is either unsafe or safe depending on the language.

4 Likes

I know about undefined variables and dangling references and such, but what threw me off was the word 'behavior'.

Ah yes. Well, normally we expect our compiled program to "behave" in the way we have written our source code when we run it. For example we expect:

x = a + b;

To set x to the sum of a and b. Or:

x =a[I];

to set x to the value of the ìth element of a`.

But how do we expect our program to behave if the summation in the first case overflows or there is no such ith element in the second case? If the language does not define what happens in these cases we have "undefined behaviour".

2 Likes

This makes sense. Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.