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

jimmykiang
This test is broken right out of the box… — FAIL: TestAgent (7.82s) agent_test.go:77: Error Trace: agent_test.go:77 agent_test.go:...
New
jesse050717
Title: Web Development with Clojure, Third Edition, pg 116 Hi - I just started chapter 5 and I am stuck on page 116 while trying to star...
New
mikecargal
Title: Hands-On Rust (Chapter 11: prefab) Just played a couple of amulet-less games. With a bit of debugging, I believe that your can_p...
New
cro
I am working on the “Your Turn” for chapter one and building out the restart button talked about on page 27. It recommends looking into ...
New
gilesdotcodes
In case this helps anyone, I’ve had issues setting up the rails source code. Here were the solutions: In Gemfile, change gem 'rails' t...
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
brunogirin
When running tox for the first time, I got the following error: ERROR: InterpreterNotFound: python3.10 I realised that I was running ...
New
Henrai
Hi, I’m working on the Chapter 8 of the book. After I add add the point_offset, I’m still able to see acne: In the image above, I re...
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
New

Other popular topics Top

AstonJ
This looks like a stunning keycap set :orange_heart: A LEGENDARY KEYBOARD LIVES ON When you bought an Apple Macintosh computer in the e...
New
AstonJ
In case anyone else is wondering why Ruby 3 doesn’t show when you do asdf list-all ruby :man_facepalming: do this first: asdf plugin-upd...
New
AstonJ
Saw this on TikTok of all places! :lol: Anyone heard of them before? Lite:
New
AstonJ
If you get Can't find emacs in your PATH when trying to install Doom Emacs on your Mac you… just… need to install Emacs first! :lol: bre...
New
PragmaticBookshelf
Build efficient applications that exploit the unique benefits of a pure functional language, learning from an engineer who uses Haskell t...
New
First poster: joeb
The File System Access API with Origin Private File System. WebKit supports new API that makes it possible for web apps to create, open,...
New
New
New
PragmaticBookshelf
Develop, deploy, and debug BEAM applications using BEAMOps: a new paradigm that focuses on scalability, fault tolerance, and owning each ...
New
AstonJ
This is cool! DEEPSEEK-V3 ON M4 MAC: BLAZING FAST INFERENCE ON APPLE SILICON We just witnessed something incredible: the largest open-s...
New

Sub Categories: