fn main() { // These lines wont work bc m1 and m2 are moved // let m1 = String::from("Hello"); // let m2 = String::from("World"); // greet(m1, m2); // let s = format!("{} {}", m1, m2); // This is suboptimal due to verboseness of solution let m1 = String::from("Hello"); let m2 = String::from("World"); let (m1_again, m2_again) = greet_ret(m1, m2); let s = format!("{} {}", m1_again, m2_again); println!("{s}"); // references make a convenient usage of ownership // Can now use mm1 and mm2 later after the call to greet_ref // References are non-owning pointers. let mm1 = String::from("Hello"); let mm2 = String::from("World"); greet_ref(&mm1, &mm2); let ss = format!("{} {}", mm1, mm2); println!("{ss}"); // ----------------------------------------------------------------------------- // Dereferencing a pointer accesses it data // Rust often implicitly inserts references and dereferences throughout // the code; ergo, won't see * often in Rust code // This can be seen in str::len let mut x: Box = Box::new(1); let a: i32 = *x; // reads heap value, so a stores 1 not following x *x += 1; // *x points to value on heap, now adds 1 to x println!("{}", *x); let r1: &Box = &x; // r1 points to x on the stack let b: i32 = **r1; // two dereferences get the heap value println!("{}", b); let r2: &i32 = &*x; // r2 points to heap value directly. Making a new reference to the deferenced i32 let c: i32 = *r2; // one dereference needed to read it // ----------------------------------------------------------------------------- // Rust avoids simultaneous aliasing and mutation // // Aliasing: accessing same data through different variables // aliasing on its own okay. Combining with mutation allows "rug pulling" from other variables // Invalid code: // let mut v: Vec = vec![1, 2, 3]; // let num: &i32 = &v[2]; // v.push(4); // println!("3rd element is {}", *num); // undefined behavior here // Data in Rust should never be aliased and mutated at the same time // ----------------------------------------------------------------------------- // References Change Permissions on Places // // Read (R): Data can be copied to another location // Write (W): Data can be mutated // Own (O): Dta can be moved or dropped // These permissions only exist in the compiler, not at runtime. // Default permission are RO, mut makes W // References can temporarily remove these permissions. // Permissions are defined on places and not just variables // A place being anything to the left of the = sign // Variables like a, Dereferences like *a, array accesses like a[0] // ----------------------------------------------------------------------------- // Borrow checker finds permissions violations // Anytime a place is used, Rust expects that place to have certain // permissions depending on the operation // ----------------------------------------------------------------------------- // mutable references provide unique and non-owning access to data let mut v: Vec = vec![1, 2, 3]; let num: &mut i32 = &mut v[2]; *num += 1; println!("Third element is {}", *num); // actually changes the 2nd element println!("Vector is now {:?}", v); // temporarily "downgrade" to a read-only reference let mut v: Vec = vec![1, 2, 3]; let num: &mut i32 = &mut v[2]; let num2: &i32 = &*num; println!("{} {}", *num, *num2); // ----------------------------------------------------------------------------- // Permissions are returned at the end of a references lifetime let mut x = 1; let y = &x; let z = *y; x += z; println!("final x val: {x}"); // ----------------------------------------------------------------------------- // Data must outlive all of its references // ----------------------------------------------------------------------------- // Summary // References provide ability to read/write data with consuming ownership. // References created with borrows (& and &mut) used dereferenced with * (often implictly) // Off to fixing ownership issues in ch4.3 } fn greet(g1: String, g2: String) { println!("{} {}!", g1, g2); } fn greet_ref(g1: &String, g2: &String) { println!("{} {}!", g1, g2); } fn greet_ret(g1: String, g2: String) -> (String, String) { println!("{} {}!", g1, g2); (g1, g2) } fn ascii_capitalize(v: &mut Vec) { let c = &v[0]; // *v has permissions R, W taken away if c.is_ascii_lowercase() { let up = c.to_ascii_uppercase(); // *v has permissions R, W is given v[0] = up; } else { // *v has R, W is given println!("Already Capitalized {:?}", v); } } fn first(strings: &Vec) -> &String { // This function introduces the flow permission, F. // F doesn't change throughout the body of a function let s_ref = &strings[0]; s_ref } // Doesn't know whether &String is a reference to either strings or default // fn first_or(strings: &Vec, default: &String) -> &String {}