RustBrock/ownership.md
2025-01-17 20:51:38 -07:00

9.8 KiB
Raw Blame History

Ownership

Basic Rules

  1. Each value in Rust has an owner
  2. There can only be one owner at a time
  3. When owner goes out of scope, the value will be dropped

Scope

Range in which a variable is valid, normally indicated by a {inside is a scope}

Variables are only valid between the time they are declared and the end of a scope

Shallow Copy

This causes a move (shallow copy) to s2 where s2 has the value previously owned by s1

    let s1 = String::from("hello");
    // s1 is no longer the owner variable of the string, move has occured to s2
    let s2 = s1; 
    // this would not work because s1 is no longer valid/has a pointer to the string
    println!("{s1}");

Deep Copy

This is a deep copy this only happens by default with primitive values Rust will not create a deep copy by default, therefore copies are cheap in terms of performance This will only copy the part on the stack, this is the same functionality as let y = x.clone()

    let x = 5;
    let y = x;

Deep Copy Heap

If you want a deep copy for things on both the stack and heap then use .clone() method Indication that some code executing has a high performance cost

    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1 = {s1}, s2 = {s2}");

Copy Trait

Will not be allowed to be implemented if Drop trait has already been implemented trying to implement a copy trait with a drop trait will cause a complimation error Drop is only used when it needs something special to leave scope

Any group of simple scalar values implement copy Such as: - Integers like u32 - Booleans with true/false values - All Float Point Values like f32, f64 - Char - Tuples only if they only contain values that implement copy themselves -

Assignment

When you assign a completely new value to something on the heap then then the drop function qill be called on the old data on the heap automaitcally

    let mut s = String::from("hello");
    // drop called on the old "hello"
    s = String::from("ahoy");
    // outputs ahoy
    println!("{s}, world!");

Function Ownership

This is the same as the move/shallow copy

fn main() {
    let s = String::from("hello");  // s comes into scope
    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here
    // if s was called here the s variable would no longer be valid/in scope
    let x = 5;                      // x comes into scope
    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward
    // x is allowed to be used here because of low cost to copy **see deep copy heap
} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.
fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.
fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.

Returning Multiple Values using a tuple

fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(s1); // s1 gives up ownership to funct
    println!("The length of '{s2}' is {len}.");
}
fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() returns the length of a String
    (s, length) // s gives up ownership to return a value
}

References

Rules

  1. At any given time, you can have either one mutable reference or any number of immutable references.
  2. References must always be valid

Giving access to a variable without transfering ownership/moving it

Can have unlimited read-only/immutable references

this is created by

let var_name: &var_to_borrow_value

Mutable References

Can only have 1 mutable reference

this is created by

let var_name: &mut var_to_borrow_value

This prevents three bad behavoirs

  • Two or more pointers access the same data at the same time.
  • At least one of the pointers is being used to write to the data.
  • Theres no mechanism being used to synchronize access to the data.

Also allowed

 {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no 
problems.
    let r2 = &mut s;

Mixing References

NOT ALLOWED IN ONE SCOPE ONLY 1 Mutable Reference PER SCOPE OR UNLIMITED IMMUTABLE PER SCOPE

REFERNECE SCOPE ENDS WHEN IT IS LAST USED

BAD CODE EXAMPLE

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

OK code, not great, only possible becasue r1 and r2 are out of scope when r3 is declared

    let mut s = String::from("hello");
    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{r1} and {r2}");
    // variables r1 and r2 will not be used after this point
    let r3 = &mut s; // no problem
    println!("{r3}");

Dangling Reference

Compiler will guarantee that this cannot happen This happens when a pointer refers to an address in memory that was freed by another variable and possibly used by something else

fn dangle() -> &String { // dangle returns a reference to a String
    let s = String::from("hello"); // s is a new String
    &s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
  // Danger!

Fixed Code

fn dangle() -> String { // dangle returns a String
    let s = String::from("hello"); // s is a new String

    s 
} // Here, s goes out of scope, and ownership is transfered and nothing other than the s namespcae is deallocated

Slices

Let you reference a continuous sequence of elements The sequence does not have an owner is reference only it is considered a slice which is a kind of reference

example use want to find the first word in a string words are seperated by a space

This has a problem the usize only makes sense for the reference to thee string as it currently is, if the value the string was references changes than usize makes no sense/is not connected to the state of the string/out of sync

// dont want ownership so reference is fine, returns usize this is the index of the space
fn first_word(s: &String) -> usize { 
    // convert string to an array of bytes so that we can go through it
    let bytes = s.as_bytes();
    // create iterator for bytes, method that returns each element in an array
    // enumerate, returns it as a part of a tuple instead which is destructed
    // i is the index and titem is the iteam itself to compair in the if statement
    // need to be a reference to that ownership doesnt change
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            // index of the space
            return i;
        }
    }
    // if there are no spaces in the string then return the whole length
    s.len()
}

String Slices

Slices must occur at the utf-8 character boundaries, slices at the character within a charcter's boundaries will cause an error This is constructed by

    let var_name = &string_to_refer[start_index..end_index]

.. is range syntx in rust

    let a = [..2]; // range starts at 0 and goes to 2
    let b = [0..2]; // the same as a in terms of range

    let c = [3..len];   // goes from 3 to the end
    let d = [3..];      // the same as c in terms of range

    let f = &s[..];       // the whole range of s, where s is some string

Example

    let s = String::from("hello world");
    let hello = &s[0..5];   // hello
    let world = &s[6..11];  // world

Revised Example with String Slice The value returned by this function is guaranteed to be related to the vairable passed in If it is not valid the complier will catch it and throw out a borrowing rule error (two reference types mixing)

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

String Literal Slices

    let s: &str = "hello World"

This is the same type as a string slice bbut it is a special kind that points to a specific part of the binary

String Slices as a parameter

to improve the function you can change the reference form &String to &str to also handle string literals without sacrifices to the base functionality this takes advantage of deref coercions

fn first_word(s: &str) -> &str 

Example Perfected

fn main() {
    let my_string = String::from("hello world");
    // `first_word` works on slices of `String`s, whether partial or whole
    let word = first_word(&my_string[0..6]);
    let word = first_word(&my_string[..]);
    // `first_word` also works on references to `String`s, which are equivalent
    // to whole slices of `String`s
    let word = first_word(&my_string);
    let my_string_literal = "hello world";
    // `first_word` works on slices of string literals, whether partial or whole
    let word = first_word(&my_string_literal[0..6]);
    let word = first_word(&my_string_literal[..]);
    // Because string literals *are* string slices already,
    // this works too, without the slice syntax!
    let word = first_word(my_string_literal);
}

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

Other Slices

This is a more general use of a slice

    let a = [1, 2, 3, 4, 5];
    let slice = &a[1..3];
    assert_eq!(slice, &[2, 3]);

This has a slice type of &[i32]