Forked from ShawnHymel/rust-ownership-examples.rs
Last active
August 25, 2025 10:42
-
-
Save jamesmunns/eb7b5680766e54411bc51f9c29a4161f to your computer and use it in GitHub Desktop.
Rust ownership and borrowing rules demonstration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Demonstration of the major ownership and borrowing rules in Rust | |
| // Run with `rustc rust-ownership-examples.rs && ./rust-ownership-examples` | |
| struct SensorReading { | |
| value: u16, | |
| timestamp_ms: u32, | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 1. Each value in Rust has an owner. | |
| fn demo_ownership() { | |
| let reading = SensorReading {value: 1, timestamp_ms: 100}; | |
| println!("{}", reading.value); | |
| println!("{}", reading.timestamp_ms); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 2. There can only be one owner at a time. | |
| fn demo_one_owner() { | |
| let reading = SensorReading {value: 2, timestamp_ms: 100}; | |
| // Transfer (move) ownership | |
| let new_owner = reading; | |
| // Error: borrow of moved value: `reading` | |
| // println!("{}", reading.value); | |
| // println!("{}", reading.timestamp_ms); | |
| // This works | |
| println!("{}", new_owner.value); | |
| println!("{}", new_owner.timestamp_ms); | |
| } | |
| fn demo_copy() { | |
| let my_array = [1, 1, 2, 3, 5, 8]; | |
| // Primitives and arrays implement the Copy trait | |
| // NOTE(AJM): Arrays only impl Copy if the item of the array impls Copy. | |
| // You might also want to explain that Copy means that any place where ownership would | |
| // be transfered, instead a copy (usually a memcpy) will occur instead. | |
| let my_copy = my_array; | |
| // Both of these work | |
| println!("{:?}", my_array); | |
| println!("{:?}", my_copy); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 3. When the owner goes out of scope, the value will be dropped. | |
| // fn print_reading(reading: SensorReading) { | |
| // // Ownership of reading is "consumed" by this function | |
| // println!("{}", reading.value); | |
| // println!("{}", reading.timestamp_ms); | |
| // // reading goes out of scope, so the value is dropped here | |
| // } | |
| fn print_reading(reading: SensorReading) -> SensorReading { | |
| // Ownership of reading is "consumed" by this function | |
| println!("{}", reading.value); | |
| println!("{}", reading.timestamp_ms); | |
| // Fix: return reading (shorthand for "return reading;") | |
| // NOTE(AJM): it might make sense to also show that expressions can | |
| // return values, like: | |
| // ```rust | |
| // let x = if x < 4 { 4 } else { x }; | |
| // ``` | |
| reading | |
| } | |
| fn demo_scope_drop_value() { | |
| // New scope | |
| { | |
| let mut reading = SensorReading {value: 3, timestamp_ms: 100}; | |
| // Error: borrow of moved value: `reading` | |
| // print_reading(reading); | |
| // Fix: return reading ownership | |
| reading = print_reading(reading); | |
| // Use `reading` after a move | |
| println!("{}", reading.value); | |
| println!("{}", reading.timestamp_ms); | |
| // TODO(AJM): might want to note that scope is lexical, and so the drop of reading | |
| // occurs here | |
| } | |
| // Error: cannot find value `reading` in this scope | |
| // println!("{}", reading.value); | |
| // println!("{}", reading.timestamp_ms); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 4. You can have either one mutable reference or any number of immutable references. | |
| // NOTE(AJM): One terminology I prefer is that `&` are "shared" references, and that | |
| // `&mut` is an "exclusive" reference. This matters more later when you want to introduce | |
| // the concept of "inner mutability", for example, atomic values can be modified through | |
| // a shared reference. | |
| fn print_borrowed_reading(reading: &SensorReading) { | |
| // We borrow reading instead of consuming ownership | |
| println!("{}", reading.value); | |
| println!("{}", reading.timestamp_ms); | |
| } | |
| fn demo_mutable_references() { | |
| let mut reading = SensorReading {value: 4, timestamp_ms: 100}; | |
| // References are easier than consuming and returing ownership | |
| // NOTE(AJM): you could likely use the term "pass by reference" and | |
| // "pass by value", which C programmers are likely familiar with. | |
| print_borrowed_reading(&reading); | |
| // Can have any number of immutable references | |
| let immut_ref_1 = &reading; | |
| let immut_ref_2 = &reading; | |
| let immut_ref_3 = &reading; | |
| println!("{}", (*immut_ref_1).timestamp_ms); // Explicit dereference | |
| println!("{}", immut_ref_2.timestamp_ms); // Automatic dereference | |
| println!("{}", immut_ref_3.timestamp_ms); | |
| // immut_refs are no longer used, so they go out of scope | |
| // NOTE(AJM): This is an example of "non lexical lifetimes": the compiler knows | |
| // that for the following line to work, the previous borrows must end, and will | |
| // shorten the lifetime of the references, which would normally last until the | |
| // end of the scope. | |
| // Only one mutable reference at a time | |
| let mut_ref_1 = &mut reading; | |
| // Error: cannot borrow `reading` as mutable more than once at a time | |
| // let mut_ref_2 = &mut reading; | |
| // println!("{}", mut_ref_2.timestamp_ms); | |
| // Error: cannot borrow `reading` as immutable because it is also borrowed as mutable | |
| // let immut_ref_4 = &reading; | |
| // println!("{}", immut_ref_4.timestamp_ms); | |
| // Change value in struct through the mutable reference | |
| mut_ref_1.timestamp_ms = 1000; | |
| println!("{}", reading.timestamp_ms); | |
| // mut_ref_1 is no longer used, so it goes out of scope | |
| // Now we can borrow again! | |
| let mut_ref_3 = &mut reading; | |
| mut_ref_3.timestamp_ms = 2000; | |
| println!("{}", reading.timestamp_ms); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 5. References must always be valid. | |
| // Error: cannot return reference to local variable `some_reading` | |
| // fn return_reading() -> &SensorReading { | |
| // let some_reading = SensorReading {value: 5, timestamp_ms: 100}; | |
| // &some_reading | |
| // } | |
| // Fix: return value with full ownership | |
| fn return_reading() -> SensorReading { | |
| // NIT(AJM), this could just be: | |
| // | |
| // ```rust | |
| // fn return_reading() -> SensorReading { | |
| // SensorReading {value: 5, timestamp_ms: 100} | |
| // } | |
| // ``` | |
| // | |
| // Clippy will lint against this. | |
| let some_reading = SensorReading {value: 5, timestamp_ms: 100}; | |
| some_reading | |
| } | |
| fn demo_valid_references() { | |
| let reading = return_reading(); | |
| println!("{}", reading.value); | |
| println!("{}", reading.timestamp_ms); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 6. If you move out part of a value, you cannot use the whole value anymore | |
| fn demo_partial_move() { | |
| let my_tuple = ( | |
| SensorReading {value: 6, timestamp_ms: 100}, | |
| SensorReading {value: 6, timestamp_ms: 101}, | |
| ); | |
| // Partially move ownership | |
| let first_reading = my_tuple.0; | |
| // Error: borrow of moved value: `my_tuple.0` | |
| // println!("{}", my_tuple.0.value); | |
| // Error: use of partially moved value: `my_tuple` | |
| // let new_owner = my_tuple; | |
| // println!("{}", new_owner.1.value); | |
| // Can print new owner | |
| println!("{}", first_reading.value); | |
| // Can move and borrow other parts | |
| println!("{}", my_tuple.1.value); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 7. Slices are references to the whole value and follow the same borrow rules | |
| fn demo_slices() { | |
| let my_array = [ | |
| SensorReading {value: 7, timestamp_ms: 100}, | |
| SensorReading {value: 7, timestamp_ms: 101}, | |
| SensorReading {value: 7, timestamp_ms: 102}, | |
| ]; | |
| // Create a slice (section of the array), borrows all of my_array immutably | |
| let slice_1 = &my_array[0..1]; | |
| // Error: cannot borrow `my_array` as mutable because it is also borrowed as immutable | |
| // let slice_2 = &mut my_array[1..3]; | |
| // | |
| // NOTE(AJM): you could mention `split_at_mut` which will allow you to break apart the | |
| // slice, and reborrow one part immutably. | |
| // Fix: we can have multiple immutable references | |
| let slice_2 = &my_array[1..3]; | |
| // Print out some of our slices | |
| println!("{}", slice_1[0].value); | |
| println!("{}", slice_2[0].timestamp_ms); | |
| println!("{}", slice_2[1].timestamp_ms); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // Main | |
| fn main() { | |
| demo_ownership(); | |
| demo_one_owner(); | |
| demo_copy(); | |
| demo_scope_drop_value(); | |
| demo_mutable_references(); | |
| demo_valid_references(); | |
| demo_partial_move(); | |
| demo_slices(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment