liveBook · Manning

Borrowing

As explained in Rust in Action: Systems programming concepts and techniques

Borrowing is Rust's mechanism for temporarily accessing a value through a reference without taking ownership of it.

Borrowing means accessing a value without taking ownership of it. The owner remains responsible for cleanup, while a reference lets another part of the program use the value during a valid lifetime. section 4.0

Why borrow instead of move?

Suppose check_status(sat_a) takes a CubeSat by value. Ownership moves into the function, and the value is dropped when the function returns. Calling check_status(sat_a) again fails: the variable is still in scope, but its value is no longer available. A reference avoids requesting ownership when the function only needs access. section 4.1.1 section 4.5.1

Choose the access the function needs:

  • Use &T for read-only access. section 4.5.1
  • Use &mut T for read-write access. Only one read-write borrow can exist at a time. section 4.5.1 section 4.6
  • Take ownership when the function needs it; borrowing is not a replacement for every move. section 4.5.1

Send a message without consuming the satellite

A send method can take to: &mut CubeSat and push a message into to.mailbox.messages. The caller passes &mut sat_a, then can call sat_a.recv() afterward. The satellite stays owned by the caller, while the message moves into its mailbox. section 4.5.1

Listing 4.13 Avoiding ownership issues with references
 #[derive(Debug)]
 struct CubeSat {
   id: u64,
   mailbox: Mailbox,
 }
 
 #[derive(Debug)]
 struct Mailbox {
   messages: Vec<Message>,
 }
 
 type Message = String;
 
 struct GroundStation;
 
 impl GroundStation {
     fn send(&self, to: &mut CubeSat, msg: Message) {
         to.mailbox.messages.push(msg);
     }
 }
 
 impl CubeSat {
     fn recv(&mut self) -> Option<Message> {
         self.mailbox.messages.pop()
     }
 }
 
 fn main() {
     let base = GroundStation {};
     let mut sat_a = CubeSat {
       id: 0,
       mailbox: Mailbox {
         messages: vec![],
       },
     };
 
     println!("t0: {:?}", sat_a);
 
     base.send(&mut sat_a,
               Message::from("hello there!"));     #1
 
     println!("t1: {:?}", sat_a);
 
     let msg = sat_a.recv();
     println!("t2: {:?}", sat_a);
 
     println!("msg: {:?}", msg);
 }

Follow the satellite’s mailbox from empty, to holding a message, to empty again after recv(). The complete listing shows the mutable reference at the call site and in the method signature. section 4.5.1

How Rust in Action: Systems programming concepts and techniques covers it

How deep it goes
Explains read-only and mutable reference syntax through a worked mailbox program and states the one-read-write-borrow limit.
Examples
Contrasts a repeated satellite status call that fails after a move with a mailbox that uses &mut CubeSat to send messages.
What it assumes
Builds from functions, structs, and passing values while explaining ownership, lifetimes, and borrowing as interrelated concepts.
What you can do after
Diagnose/debug moved-value errors and use read-only or mutable references when a function needs access rather than ownership.
How it connects
Connects borrowing to ownership, lifetimes, the borrow checker, mutable references, and moves.

Also explained in

Compare how 3 books explain borrowing →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Rust in Action: Systems programming concepts and techniques. The section links point to the parts of the book it draws on.