9.8 KiB
Ownership
Basic Rules
- Each value in Rust has an owner
- There can only be one owner at a time
- 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
- At any given time, you can have either one mutable reference or any number of immutable references.
- 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.
- There’s 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]