fn main() {
    let mut s: String = String::from("rust");
    // &s is a reference to s, it does NOT have ownership of s.
    // this is an immutable reference, we cannot alter s using this reference
    print_first_char(&s); // when &s goes out of scope s is NOT removed.
    alter_string(&mut s); // mutable reference to s, can alter s.
 
    // There can only be one mutable reference to a particular thing at a time
    // let r1 = &mut s; // No problem (the &mut s was used up by alter_string and got deleted)
    // let r2 = &mut s; // Problem
 
    // println!("{r1} {r2}"); // results in ERROR as r1 a mutable reference still exists
    // This is by design in rust, this is to prevent data race conditions, where two mutable
    // references might try to modify the underlying value at the same time.
    
    // let r3 = &s;     // no problem
    // let r4 = &s;     // no problem
    // let r5 = &mut s; // problem
 
    // println!("{r3} {r4} {r5}"); // error
    // This happens because we cannot have a mutable reference to a thing when immutable references
    // to it are still there because the users of the immutable reference don't expect the
    // underlying value to change.
    
    let r6 = &s; // no problem
    let r7 = &s; // no problem
    println!("{r6} {r7}"); // immutable references have been used here, so above problem won't occur
 
    let r8 = &mut s; // no problem
    println!("{r8}");
 
    println!("s = {s}");  // s is still valid since it's ownership never changed.
}
 
fn print_first_char(string: &String) {
    // We can use s here, but this function does NOT have ownership of s so when it goes
    // out of scope s will NOT be removed.
    println!("{}", string.chars().collect::<Vec<_>>()[0]);
}
 
fn alter_string(string: &mut String) {
    string.push_str(" language");
}
 
// This causes an error because &s will get deallocated when this function goes out of scope
// so the returned reference would point to an invalid address in memory.
// fn dangling() -> &String {
//     let s = String::from("dangling.");
//     &s
// }
 
// Instead we can just return the ownership (in case of a type which implements Drop trait)
// or a copy of the value (in case of a type which implements Copy trait)
fn no_dangling() -> String {
    let s = String::from("no dangling.");
    s
}