Hi All! Welcome to the Reading Club for Rust’s “The Book” (“The Rust Programming Language”). This is week 1 (the beginning!!).
Have a shot at going through “the reading” and post any thoughts, confusions or insights here
“The Reading”
-
Finish up to Chapter 2: “Programming a Guessing Game”
-
The Book: https://rust-book.cs.brown.edu/title-page.html (the special Brown University version with quizzes etc)
The Twitch Stream
- @sorrybookbroke@sh.itjust.works ran a twitch stream on these chapters which is now on YouTube: https://www.youtube.com/watch?v=ou2c5J6FmsM
- You might prefer watching and listening to that rather than reading the book.
- Be sure to catch future streams (will/should be weekly: https://www.twitch.tv/deerfromsmoke)
What’s Next Week?
- Chapters 3 and 4
- Start thinking about challenges or puzzles to try as we go in order to get some applied practice!
- EG, Advent of Code
- Maybe some basic/toy web apps such as a “todo”
Even though The Book is a bit verbose in these first few sections and really only touches on the basics of the language, I enjoyed going through it! Once you’ve gone to the end of just chapter 2, you’ve touched on project management with cargo
, compiling with rustc
or cargo
, match
statements, and some of the other syntactical details of the language. It’s definitely enough to get you started if you’re new to the language!
For me, my outstanding questions, which come from the final exercise that builds a “guessing game” (code extracted below for easy reference):
- Why are macros covered so much later in the book (ch 19)? … not for mere mortals?
- I don’t think I really know at all what an object like
Ordering
actually is and what mechanically happened when it was used in the match statement. - Why did I import/use
rand::Rng
but then writerand.thread_rng().gen_range()
. That I’m importing something not explicitly used in the code (Rng
is never used) feels off to me. I can only guess that this was a shortcut to get to use a higher level interface. But what isRng
? - This will probably come up when we cover “Ownership” … but it strikes me now that we were passing variables by reference (eg
and
. Given rust’s concern with ownership and the “borrow checker” as a means of preventing memory safety issues … why isn’t it the default behaviour that a variable is passed by reference? Why do we have to explicitly pass the reference ourselves (with the ampersand syntax
)?
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
// println!("Secret number is: {secret_number}");
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
// let guess: u32 = guess.trim().parse().expect("Please type a number!");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("you guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win");
break;
}
}
}
}
This is just my attempt at answering (I’m learning too):
Macros are easy to use, allowing beginners to write ‘hello world’ for example, but hide a bunch of complicated stuff we’re unlikely to understand until later. (I vaguely remember from Java that just writing something to the console was a whole bunch statements strung together.)
I found it useful to document what each line was doing in the example, to get my head around the terminology.
std
is a crate, io
is a module in the std
crate, and that module provides methods like stdin()
std
is a crate, cmp
is a module in the std
crate, and that module provides enums like Ordering
rand
is a crate, Rng
is a trait from the rand
crate, and that trait provides methods like thread_rng()
Ordering
is a enum - a type with a list of variants with developer-friendly names. In the same way that a ‘DaysOfTheWeek’ enum would have ‘Monday’, ‘Tuesday’ …, etc, Ordering
has Less, Greater, or Equal. match
statements work with enums, in a ‘for this, do that’ kind of way.
Rng
is a trait, which feature a lot in Rust, but is something I’ve had difficulty getting my head around. Where I’m at so far is they mostly provide convenience. The rand
create provides a number of structures in its rngs
module - ThreadRng
, OsRng
, SmallRng
, and StdRng
, depending on what you want to use to generate randomness. Instead of saying ThreadRng
provides the gen_range()
method, and OsRng
provides the gen_range()
method, etc, Rust just says they implement the Rng
trait. Since Rng
provides the gen_range()
method, it means that everything that implements it will provide it.
thread_rng()
returns a ThreadRng
structure, which provides gen_range()
via the Rng
trait, but we need to bring that trait into scope with the use
keyword in order to be able to call it.
For the default behaviour of passing owned vs. borrowed variables, I guess it’s useful to explicitly state “I’m giving this variable to another function, because I don’t intend to use it anymore”, so that if you inadvertently do, the compiler can catch it and error.
Thanks!!
Rng
is a trait, which feature a lot in Rust, but is something I’ve had difficulty getting my head around. Where I’m at so far is they mostly provide convenience. Therand
create provides a number of structures in itsrngs
module -ThreadRng
,OsRng
,SmallRng
, andStdRng
, depending on what you want to use to generate randomness. Instead of sayingThreadRng
provides thegen_range()
method, andOsRng
provides thegen_range()
method, etc, Rust just says they implement theRng
trait.
This makes a lot of sense actually. Thanks! It does lead to the awkward situation where you’d have to know that Rng is the underlying trait of the rest of the module and so import it. But like I said, I’m guessing that’s because this example is aiming only for easy high-level usage of rand
. I’d guess there’s a lower level way of using the rand
crate that would involve more direct imports, perhaps use rand::ThreadRng
?
For the default behaviour of passing owned vs. borrowed variables, I guess it’s useful to explicitly state “I’m giving this variable to another function, because I don’t intend to use it anymore”, so that if you inadvertently do, the compiler can catch it and error.
Makes sense.
Sooo … is passing by value a thing in rust? Or does just about every method take only reference types as arguments?
Sooo … is passing by value a thing in rust? Or does just about every method take only reference types as arguments?
I think this is an occasion where a vague familiarity with other languages ended up confusing me with Rust. The ‘&’ sign doesn’t mean ‘pass by reference’ in the same way as it does in C. Anything with a size that’s fixed at compile time is typically passed by value, whereas variables who’s size might change are passed by reference. The ‘&’ in Rust isn’t about that. For variables that are passed by reference, the ‘&’ is about whether the ownership of that memory address is transferred or not.
To illustrate:
fn abc(v: String) {
println!("v is {}", v);
}
fn main() {
let mut v=String::from("ab");
v.push('c');
abc(v);
// println!("v is {}", v);
}
works fine as it is, but will error if you uncomment the second println! The ‘v’ variable was passed by reference, but it’s ownership was transferred, so it can’t be referred to again.