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”

The Twitch Stream

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”
  • maegul (he/they)@lemmy.mlOPM
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    8 months ago

    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. 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.

    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?

    • freamon@endlesstalk.org
      link
      fedilink
      English
      arrow-up
      3
      ·
      edit-2
      8 months ago

      I wondered the same, and tried use rand::rngs::ThreadRng but the compiler wouldn’t accept it, and suggest I use the trait instead. So it looks like the compiler can be helpful in identifying these things (and maybe traits are the first thing that Rust developers look for when reading the docs for a crate).

      (wrongness edited out and hopefully corrected in new comment)

      • maegul (he/they)@lemmy.mlOPM
        link
        fedilink
        English
        arrow-up
        3
        ·
        8 months ago

        and maybe traits are the first thing that Rust developers look for when reading the docs for a crate

        Yep. Makes a lot of sense. Probably gotta start thinking in terms of traits at some point. I haven’t spun up any LSP yet but hopefully that can help surface these sorts of things.

        Still, at the moment, it does seem like a wrinkle in the usability of the language that you import something which implicitly underlies what you actually want to use.

        Also, Thanks!

    • freamon@endlesstalk.org
      link
      fedilink
      English
      arrow-up
      3
      ·
      8 months ago

      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.

      • maegul (he/they)@lemmy.mlOPM
        link
        fedilink
        English
        arrow-up
        2
        ·
        edit-2
        8 months ago

        Thanks!

        Seems I gotta dig into the borrow checker before thinking too much about this!

        Otherwise, compiling your code snippet is a nice illustration of how helpful the compiler tries to be … lots of tips in the output there! To anyone else, just try running rustc on this, with the second println! uncommented and see the output, which is half error half linting.


        For variables that are passed by reference, the ‘&’ is about whether the ownership of that memory address is transferred or not.

        Yea. So if the second println! were uncommented, how could we compile this? From what you’ve said, I’d guess that & means “borrow” (ie, not “move” ownership).

        So if we alter abc to take a &String type and not String, and therefore only “borrow” the variable, and then pass in &v and not v to pass in a “borrowed” variable, it should compile.

        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);
        }
        

        It seems to!

        Of course, as the compiler suggests, we could instead just pass in v.clone() which presumably creates a new variable and effectively “passes by value”.


        Digging in a bit more, what happens if abc (tries to) mutate the variable?

        We can add v.push('X') to abc and see if we get different printouts. As the compiler would tell us, we would need to make the argument v mutable for this to work.

        fn abc(mut v: String) {
        
            v.push('X');
            println!("v is {}", v);
        }
        
        fn main() {
            let mut v=String::from("ab");
            v.push('c');
            abc(v.clone());
        
            println!("v is {}", v);
        }
        
        // OUTPUT:
        // v is abcX
        // v is abc
        

        I’m not clear on why I don’t have to declare that the v.clone() is mutable in anyway though.

        What about trying the same with a "borrowed’ variable?

        Well we need mutable borrowed variables, so necessary adjustments to the types of abc and its call in main. And adding an additional mutation of v in main after abc is called, and we get two different println outputs, with each mutation applying to the same variable.

        fn abc(v: &mut String) {
        
            v.push('X');
            println!("v is {}", v);
        }
        
        fn main() {
            let mut v=String::from("ab");
            v.push('c');
        
            abc(&mut v);
        
            v.push('Y');
        
            println!("v is {}", v);
        }
        
        // OUTPUT
        // v is abcX
        // v is abcXY
        
        • ericjmorey@programming.dev
          link
          fedilink
          English
          arrow-up
          2
          ·
          8 months ago

          Seems I gotta dig into the borrow checker before thinking too much about this!

          It’s covered in detail in chapter 4.