Chocrates

Chocrates

Hands-on Rust: Chapter 7 Invalid Camera Method from Chapter 5

In the end of chapter 7 we write the movement function in movement.rs
We call the camera on_player_move method like so:

camera​.on_player_move​(&want_move.destination);

This results in a compile error on my machine because on_player_move expects a reference.

The latest code in the book I see for the camera method is back in Chapter 5

 	    ​pub​ ​fn​ ​on_player_move​(&​mut​ ​self​, player_position: Point) {
​ 	        ​self​.left_x = player_position.x ​-​ DISPLAY_WIDTH​/​2;
​ 	        ​self​.right_x = player_position.x + DISPLAY_WIDTH​/​2;
​ 	        ​self​.top_y = player_position.y ​-​ DISPLAY_HEIGHT​/​2;
​ 	        ​self​.bottom_y = player_position.y + DISPLAY_HEIGHT​/​2;
​ 	    }

Looking at the code zip it appears that the function is always expecting a reference, though I got a bit lost on the folder names so it could be wrong there too.

Changing the method to expect a reference fixes the compile error and it appears to run fine.

I didn’t test it but I think the method could take in either a Point or an &Point and as long as the method is called the same it should be fine? If this is true, is this because destination always is a calculated value that doesn’t need to worry about the borrow checker?

Marked As Solved

herbert

herbert

Author of Hands-on Rust

Thanks for finding that - great catch! I’m working on clearing that one up now, so it should make beta 2. It looks like an error slipped in when I merged some code branches on my machine (the bundled source compiles because the on_player_move signature changed without being mentioned in the text).

It’ll work either way, so long as the function is either &Point or Point and the calling code follows the same convention. The correct form is just Point. The reasoning behind this is a little complicated (more so than I can justify adding into a beginner’s book), so I’ll try and explain it.

The Point structure (source) has a few properties:

  • It’s internal representation is two i32, 32-bit signed integers.
  • It derives Copy to make it “trivially copyable”.
  • It implements all manner of niceties to work with other libraries and use a crate named Ultraviolet under the hood for fast SIMD math when you use it as a (math-style) vector.

Since the type is two 32-bit integers, its memory representation is 64-bits long. That’s really helpful, because on 64-bit architectures (most PCs these days) it fits into a single register. Copying it to a function becomes a VERY fast operation - place it in a register and off it goes. Even without a register, copying a single 64-bit value is a ridiculously fast memcpy operation under the hood. The fun part is that sending a reference is actually slower in this case! References are really pointers under the hood. So the system sends a pointer to the value to the function. The pointer is 64-bits long, so the pointer is the exact same size. But the function then has to de-reference the pointer - lookup the memory address being pointed to, and get the value from there. So the computer has to perform an extra step to get to the value. There’s a Clippy warning in “pedantic” mode that will warn you about this sometimes.

When you compile in release mode, LLVM (the underlying compiler system) is often smart enough to transform a read-only pointer into a copy. It’s also smart enough to realize that if it puts the value into a register earlier and “inlines” the function call (literally copy it inline into the calling code) it can skip copying/passing altogether. It doesn’t always get that right, but it usually does. (“RVO - Return Value Optimization” can cause “copy elision” to happen, often eliminating the entire copy. It’s not guaranteed to happen in Rust, but it usually does)

So, that’s great - but there are times you want to be referencing Point rather than copying it. Every time you are getting one out of Legion, a reference is preferable because you are operating on the original value in Legion’s data store. If you are updating the point (with an &mut) you absolutely have to use a reference - otherwise your updates won’t apply to the original. When you are accessing a bunch of Point values, you tend to gain more from them being adjacent in cache than you lose from the pointer jump - a jump to something in L1 cache is effectively instant.

Hope that wasn’t too long an explanation. TLDR: it’s fixed for beta 2, thank you for spotting it.

Also Liked

Chocrates

Chocrates

Thanks that helps.

Would a function ever declare a mut parameter instead of &mut? Is that even possible or would ever make sense?
Feel free to tell me to pound sand if you don’t want to answer those types of questions here :slight_smile:

herbert

herbert

Author of Hands-on Rust

That’s a fun one. :slight_smile: I’ve written a few functions with mutable, non-reference parameters - usually when porting directly from C++ and its ilk. It’s a good way to let the function modify the parameter without side-effects - that is, the changes to the parameter never leave the function.

Say you had a function:

fn do_stuff(mut n: i32) {
   while n > 0 {
      // Do something with n here!
      n -= 1;
   }
}

When you call do_stuff, it modifies n inside the function - but the original n that is passed in never changes. n is either a copy or a move depending upon the type - but it is now local to the function, just as if you’d typed let mut n = 5 as the first line of the function. So when you call it:

let mut n = 5;
do_stuff(n);
println!("{}", n);

You still get the output of 5, because n is copied (i32 copies by default - most primitives do). Whereas if you’d use a mutable reference, you’d get:

fn do_stuff(n: &mut i32) {
   while n > 0 {
      // Do something with n here!
      n -= 1;
   }
}

..
let mut n = 5;
do_stuff(&mut n);
println!("{}", n);

You will get the output 0, because the original n was modified.

I often think of mutable borrow parameters as “out” parameters (some other languages use that terminology) - because they are effectively returning something, even if they are doing it through a parameter rather than a return statement.

Hope that helps! (Sorry about the edits; I wrote it on my phone the first time, and Rust isn’t well suited to mobile keyboards)

Where Next?

Popular Pragmatic Bookshelf topics Top

lirux
Hi Jamis, I think there’s an issue with a test on chapter 6. I own the ebook, version P1.0 Feb. 2019. This test doesn’t pass for me: ...
New
raul
Page 28: It implements io.ReaderAt on the store type. Sorry if it’s a dumb question but was the io.ReaderAt supposed to be io.ReadAt? ...
New
leba0495
Hello! Thanks for the great book. I was attempting the Trie (chap 17) exercises and for number 4 the solution provided for the autocorre...
New
leonW
I ran this command after installing the sample application: $ cards add do something --owner Brian And got a file not found error: Fil...
New
adamwoolhether
I’m not quite sure what’s going on here, but I’m unable to have to containers successfully complete the Readiness/Liveness checks. I’m im...
New
Charles
In general, the book isn’t yet updated for Phoenix version 1.6. On page 18 of the book, the authors indicate that an auto generated of ro...
New
akraut
The markup used to display the uploaded image results in a Phoenix.LiveView.HTMLTokenizer.ParseError error. lib/pento_web/live/product_l...
New
mert
AWDWR 7, page 152, page 153: Hello everyone, I’m a little bit lost on the hotwire part. I didn’t fully understand it. On page 152 @rub...
New
tkhobbes
After some hassle, I was able to finally run bin/setup, now I have started the rails server but I get this error message right when I vis...
New
EdBorn
Title: Agile Web Development with Rails 7: (page 70) I am running windows 11 pro with rails 7.0.3 and ruby 3.1.2p20 (2022-04-12 revision...
New

Other popular topics Top

Devtalk
Hello Devtalk World! Please let us know a little about who you are and where you’re from :nerd_face:
New
PragmaticBookshelf
Learn from the award-winning programming series that inspired the Elixir language, and go on a step-by-step journey through the most impo...
New
DevotionGeo
I know that these benchmarks might not be the exact picture of real-world scenario, but still I expect a Rust web framework performing a ...
New
DevotionGeo
I know that -t flag is used along with -i flag for getting an interactive shell. But I cannot digest what the man page for docker run com...
New
Rainer
My first contact with Erlang was about 2 years ago when I used RabbitMQ, which is written in Erlang, for my job. This made me curious and...
New
PragmaticBookshelf
Tailwind CSS is an exciting new CSS framework that allows you to design your site by composing simple utility classes to create complex e...
New
AstonJ
If you are experiencing Rails console using 100% CPU on your dev machine, then updating your development and test gems might fix the issu...
New
AstonJ
If you want a quick and easy way to block any website on your Mac using Little Snitch simply… File > New Rule: And select Deny, O...
New
PragmaticBookshelf
Author Spotlight: VM Brasseur @vmbrasseur We have a treat for you today! We turn the spotlight onto Open Source as we sit down with V...
New
PragmaticBookshelf
Programming Ruby is the most complete book on Ruby, covering both the language itself and the standard library as well as commonly used t...
New

Sub Categories: