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

jimschubert
In Chapter 3, the source for index introduces Config on page 31, followed by more code including tests; Config isn’t introduced until pag...
New
Alexandr
Hi everyone! There is an error on the page 71 in the book “Programming machine learning from coding to depp learning” P. Perrotta. You c...
New
Mmm
Hi, build fails on: bracket-lib = “~0.8.1” when running on Mac Mini M1 Rust version 1.5.0: Compiling winit v0.22.2 error[E0308]: mi...
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
jgchristopher
“The ProductLive.Index template calls a helper function, live_component/3, that in turn calls on the modal component. ” Excerpt From: Br...
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
digitalbias
Title: Build a Weather Station with Elixir and Nerves: Problem connecting to Postgres with Grafana on (page 64) If you follow the defau...
New
AufHe
I’m a newbie to Rails 7 and have hit an issue with the bin/Dev script mentioned on pages 112-113. Iteration A1 - Seeing the list of prod...
New
jwandekoken
Book: Programming Phoenix LiveView, page 142 (157/378), file lib/pento_web/live/product_live/form_component.ex, in the function below: d...
New
redconfetti
Docker-Machine became part of the Docker Toolbox, which was deprecated in 2020, long after Docker Desktop supported Docker Engine nativel...
New

Other popular topics Top

Devtalk
Reading something? Working on something? Planning something? Changing jobs even!? If you’re up for sharing, please let us know what you’...
1052 22283 402
New
New
brentjanderson
Bought the Moonlander mechanical keyboard. Cherry Brown MX switches. Arms and wrists have been hurting enough that it’s time I did someth...
New
AstonJ
poll poll Be sure to check out @Dusty’s article posted here: An Introduction to Alternative Keyboard Layouts It’s one of the best write-...
New
PragmaticBookshelf
Rust is an exciting new programming language combining the power of C with memory safety, fearless concurrency, and productivity boosters...
New
PragmaticBookshelf
Learn different ways of writing concurrent code in Elixir and increase your application's performance, without sacrificing scalability or...
New
New
PragmaticBookshelf
Author Spotlight Jamis Buck @jamis This month, we have the pleasure of spotlighting author Jamis Buck, who has written Mazes for Prog...
New
AstonJ
If you’re getting errors like this: psql: error: connection to server on socket “/tmp/.s.PGSQL.5432” failed: No such file or directory ...
New
PragmaticBookshelf
A concise guide to MySQL 9 database administration, covering fundamental concepts, techniques, and best practices. Neil Smyth MySQL...
New

Sub Categories: