Ownership
- Each value must always have one and only one variable pointing to it, that is its owner.
- At the end of scope of the owner, the value is dropped (i.e.) memory is deallocated.
- Ownership is
moved
by default, but can be copied usingCopy
.
Moving and Copying
- Primitive values like int, bool are copied by default.
Borrowing
- Borrowing allows temporary references (&T or &mut T) without transferring ownership.
- At any given time there can only be either a mutable or an unmutable reference.
- A reference must always be valid.
!(Mut + UnMut)
- Cannot have mutable and unmutable ref for the same value at the same time.
- Cannot have multiple mutable ref, to avoid race mutations to same value, if both ref are used to modify the same value. Avoids corrupting data.
- Can have multiple unmutable ref.
Invalid reference
Following fails because βaβ is out of scope at the end of dangling fn and will be cleared. So a reference to a non existing value is not possible. (i.e.) cannot borrow from dropped value.
fn main() {
let aref = dangling();
}
fn dangling() -> &String {
let a = String:from("Hello");
&a;
}
Slice
fn main() {
let s = String::from("Hello world");
let fw = first_word(&s);
println!("{}", fw);
}
fn first_word(s: &String) -> &str {
for (i, &item) in s.as_bytes().iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
return &s[..];
}
Why &s[..] instead of s[..]?
-
s[..] means βslice from 0 to endβ
-
s[..] derefs s: &String to String, then calls -
Deref<Target=str>
-
So s[..] gives a str (unsized)
-
Wrapping with &: &s[..] is a &str (sized, usable)
-
String owns heap memory
-
&String borrows the whole struct
-
&str borrows just the actual text (slice of bytes)
-
You need &s[..] to convert a &String to a &str
Expression | Type | Notes |
---|---|---|
s | &String | Reference to the whole String |
*s | String | Dereferenced String |
(*s)[..] | str | Underlying string slice |
&s[..] | &str | Reference to string slice β |
Modules
Scenario 1: Seprate modul.rs file
- DO NOT wrap it in
mod modul { ... }
- Instead, declare it in main.rs using mod modul;
- Then use it with use modul::β¦ or modul::β¦
Layout
With this example layout
src/
βββ main.rs
βββ modul.rs
mod modul; // loads src/modul.rs
use modul::Breakfast;
fn main() {
let summer_bf = Breakfast::summer_menu();
println!("{:?}", summer_bf);
}
#[derive(Debug)]
pub struct Breakfast {
pub food: String,
pub drink: String,
}
impl Breakfast {
pub fn summer_menu() -> Breakfast {
Breakfast {
food: "Toast".into(),
drink: "Orange juice".into(),
}
}
}
mod modul; tells the compiler to load the external file, and the file must not declare mod modul again β that would be redundant and cause a nested path like modul::modul::Breakfast.
Scenario 2: You define the module inline inside main.rs
- DO use
mod modul { ... }
directly - No need for mod modul; or separate file
- You can still use modul::β¦ if you like
mod modul {
#[derive(Debug)]
pub struct Breakfast {
pub food: String,
pub drink: String,
}
impl Breakfast {
pub fn summer_menu() -> Breakfast {
Breakfast {
food: "Toast".into(),
drink: "Orange juice".into(),
}
}
}
}
use modul::Breakfast;
fn main() {
let summer_bf = Breakfast::summer_menu();
println!("{:?}", summer_bf);
}
Seprate folder sub modules
src/ βββoutermodul/ | βββ innermodul.rs βββ outermodul.rs β> use innermodul; βββ main.rs β> use outermodul;
If there is an inner modul, that needs to present in a folder with same name as the outermodul. This can then be access by outermodul.
If adding a bunch of utils to same folder
rust expects mod.rs file in directory.
src/
βββ utils/
βββ mod.rs
βββ math.rs
βββ stringconcat.rs
βββ extracttoken.rs
Other files that we want to import into other files must be brought into scope in the mod.rs file.
pub mod math;
pub mod stringconcat;
pub mod extracttoken;
In main.rs
mod utils; // loads utils/mod.rs and submodules
fn main() {
// To access math and concat functions:
let sum = utils::math::add(1, 2);
let combined = utils::stringconcat::concat_str("foo", "bar");
}
Scenario | How to declare submodules in mod.rs | How to declare in main.rs | How to use modules |
---|---|---|---|
Folder with mod.rs | pub mod math; pub mod concat; in mod.rs | mod utils; in main.rs | utils::math::func() |
Flat files no mod.rs and no foder | N/A | mod math; mod concat; in main.rs | math::func() , concat::func() |
Copy and move
In the following example, both
println!("{}", &v[0]);
println!("{}", v[0]);
will work.
pub fn collections() {
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3, 4];
v.push(1);
println!("{}", &v[0]);
}
When using v[0]
or &v[0]
-
i32 by default βcopiesβ to another variable when it is assigned.
-
String and structs when assign to another var need to be passed as ref, else they will be βmovedβ on default. Can be cloned with .clone(). Compile err when trying to access the same after move, if not passed as ref initially.
-
println
accepts ref of variable.println!("{}", x);
will auto-borrow x if it implements Display by reference. E.g. String, Vec, custom structs, if v[0] is used. If not, it will accept a clone (internally), If String is passed, will auto deref to &String which implements Display interface. -
println!("{}", v[0]);
will work even if vector had a string at index 0, because by default [] indexing fn returns the reference not the owner and the auto deref will automatically use the &String from String. -
But
let s = vStr[0];
will not compile if idx 0 is string- We get a reference like before, but String type cant copy by default
- But moving from a vector isnt allowerd.
-
This is why
let a = vStr[0];
doesnt work but bothprintln!("{}", &v[0])
andprintln!("{}", v[0])
work.
From ChatGPT:
Code | Borrowed? | Moved? | Why it works |
---|---|---|---|
println!("{}", v[0]) | β yes | β no | v[0] returns reference; auto-borrowed |
println!("{}", &v[0]) | β yes | β no | Explicitly borrowed |
println!("{}", v[0].clone()) | β no | β yes | Clones and moves the cloned value |
let x = v[0]; | β no | β no | β Fails for non-Copy types like String |
Generics With more Ownership understandings
Functions accessible based on their traits
Below is an example of a generic scenario where based on the type/ trait of the incoming type, the object gets access to different methods.
#[derive(Debug)]
struct Point<T,U> {
x: T,
y: U,
}
impl<T: Copy, U> Point<T,U> { // get_x is accessible only for variables with copy trait
fn get_x(&self) -> T {
self.x
}
}
impl<T> Point<T,f64> {
fn get_floaty(&self) -> f64 {
self.y
}
}
fn enumgenr() {
let flotPt = Point { x: 5, y: 3.5 };
flotPt.get_floaty();
flotPt.get_x();
let intPt = Point { x: 5, y: 3 };
// intPt.get_floaty(); // wont work
intPt.get_x();
}
Complex Generic usecase
We try to combine two different generics and create an output combining their types.
Calling function to give context
- All points contains x and y
- There are two examples here one with a method for copy trait and another with clone trait.
- Both examples have two points each, and combine x from pt1 and y from pt2.
fn mixup() {
// Copy trait
let a = Point {x: 1, y: 3.0};
let b = Point {x: "Hello", y: 'c'};
let c = a.mix(b);
println!("{:?}", c);
println!("{:?}", a);
// Clone trait
let d = Point {x: String::from("tasd"), y: 3.0};
let e = Point {x: String::from("Hello"), y: 'c'};
let f = a.mix(b);
println!("{:?}", f);
println!("{:?}", d);
}
Copy Trait
- Supports T with copy trait, e.g. int, char, static str (stored on stack, fixed size).
- &self is a ref. Always copies. If βselfβ instead of β&selfβ was used, it means that struct βaβ is moved into the method.
- Which means a is no longer usable after this fn call. This is not because of the copy itself but simply how ownership is transferred if reference isnβt passed.
impl <T: Copy, U> Point<T, U> {
fn mix<V, W>(&self, pt: Point<V, W>) -> Point<T, W> {
Point {x: self.x, y: pt.y }
}
}
Clone Trait
- Support T with clone trait. e.g. String, heap allocated, can grow.
- Here the value needs to be explicitly cloned since its not allowed to be moved out of a reference.
- Since &self is a shared self reference.
impl <T: Clone, U> Point<T, U> {
fn mix<V, W>(&self, pt: Point<V, W>) -> Point<T, W> {
Point {x: self.x.clone(), y: pt.y }
}
}
Clone trait without &self
- Support T with Clone trait, e.g. String (heap-allocated, not Copy).
- This uses
self
instead of&self
, meaning the method takes ownership of the whole struct. - Because
self
is owned (not a reference), we are allowed to movex
out of it directly. - No need to clone here, even though T isnβt Copy, because the method owns the value and value is moved.
- Any method trying to access the struct after passing to method will cause a compiler error
impl <T: Clone, U> Point<T, U> {
fn mix<V, W>(self, pt: Point<V, W>) -> Point<T, W> {
Point {x: self.x, y: pt.y }
}
}
&x.y always means βtake a reference to x.yβ, regardless of whether x is already a reference or not.