[Avg. reading time: 19 minutes]
Structs
A struct is a custom data type used to group related values.
Like a tuple, it can hold mixed data types. Unlike a tuple, each field has a name, making code more readable and maintainable.
Accessing Data
- Tuple : Access using .0, .1
- Struct : Access using meaningful names like student.id
Tuple Recap
fn main() { let person_data = ("Rachel", 10, "DB"); println!("{}",person_data.0); println!("{}",person_data.1); println!("{}",person_data.2); }
Classic Struct
- Most commonly used
- Each field has a name and a type
- Struct name follows PascalCase convention
struct Student { id: i32, name: String, course: String, } fn main() { let s = Student { id: 10, name: String::from("Rachel"), course: String::from("DB"), }; println!("{},{},{}", s.id, s.name, s.course); }
Structs are typically declared outside main, unless their scope is strictly local.
Where the data is store Stack or Heap?
It depends on the fields inside the struct.
Lets find out where the objects inside Struct are created.
#[allow(dead_code)] struct Students { id: i32, name: String, course: String, } fn main() { let s: Students = Students { id: 10, name: String::from("Rachel"), course: String::from("DB"), }; println!( "Stack Address of s -> {:p} \ \nid value -> {} \ \nStack address of s.id -> {:p} \ \nStack Address of s.name -> {:p} \ \nStack Address of s.course -> {:p} \ \ns.name pointing to Heap -> {:p} \ \ns.course pointing to Heap -> {:p}", &s, s.id, &s.id, &s.name, &s.course, s.name.as_ptr(), s.course.as_ptr() ); }
Stack:
---------
| id (10) |
---------
| name (pointer, length, capacity) |
---------
| course (pointer, length, capacity) |
---------
Heap:
--------------------------------
| "Rachel" (string data) |
--------------------------------
| "DB" (string data) |
--------------------------------
- The struct itself lives on the stack.
- Heap-allocated fields (like String, Vec) store their actual data on the heap.
- The struct stores only metadata (pointer, length, capacity).
Struct - Traits
Printing the Struct value using Debug Trait
struct Students { id: i32 } fn main() { let stu: Students = Students { id: 10 }; println!("{:d}",stu) }
Assigning Struct to another variable
struct Students { id: i32 } fn main() { let stu: Students = Students { id: 10 }; let new_student = stu; println!("{}",stu.id) }
Error? Bcoz Ownership of stu move to new_student.
Cloning Struct to another variable
struct Students { id: i32 } fn main() { let stu: Students = Students { id: 10 }; let new_student = stu.clone(); println!("{}",stu.id) }
What do we see now?
method `clone` not found for this struct
As Student is a user defined Datatype, we need to add the necessary traits.
Putting together all of the above
// Struct Stack or Heap #[derive(Copy, Clone,Debug)] struct Students { id: i32 } fn main() { let stu: Students = Students { id: 10 }; let new_student = stu.clone(); let new_student1 = stu; println!("Debug output {:?}", new_student); println!("Cloned Value {}",new_student.id); println!("Moved Value {}",new_student1.id); }
Struct inside Vector
As this Struct has String Copy trait cannot be implemented.
#[derive(Debug, Clone)] struct Student { id: i32, name: String, course: String, } fn main() { let mut students: Vec<Student> = Vec::new(); students.push(Student { id: 10, name: String::from("Rachel"), course: String::from("DB"), }); students.push(Student { id: 11, name: String::from("Monica"), course: String::from("DB"), }); for s in students.iter() { println!("{},{},{}", s.id, s.name, s.course); } }
Struct implementation
Implementation blocks use impl. Uses the same name as Struct.
- Methods
- Associated functions
Let’s see an example
#[derive(Debug, Clone)] struct Students { id: i32, name: String, course: String, } impl Students { // This one takes Ownership fn get_student_details(self){ println!("{}", self.id); println!("{}", self.name); println!("{}", self.course); } // This one uses Immutable Borrow fn get_student_details1(self: &Self){ println!("{}", self.id); println!("{}", self.name); println!("{}", self.course); } } fn main() { let s: Students = Students { id: 10, name: String::from("Rachel"), course: String::from("DB"), }; let s1: Students = Students { id: 11, name: String::from("Monica"), course: String::from("DB"), }; s.get_student_details(); s1.get_student_details1(); }
Using Associated Function
Functions inside the impl block that do not take “self” as a parameter.
#[derive(Debug, Clone)] struct Students { id: i32, name: String, course: String, } impl Students { fn get_student_details(self){ println!("{}", "-".repeat(100)); println!("{}", self.id); println!("{}", self.name); println!("{}", self.course); println!("{}", "-".repeat(100)); } fn create_student(pid:i32,pname:String,pcourse:String) -> Students{ Students{id:pid,name:pname,course:pcourse} } } fn main() { let s: Students = Students { id: 10, name: String::from("Rachel"), course: String::from("DB"), }; let s1: Students = Students { id: 11, name: String::from("Monica"), course: String::from("DB"), }; s.get_student_details(); s1.get_student_details(); // Creating Student using Associated Function and printing it via Method let s2 = Students::create_student(12,"Ross".to_string(),"C++".to_string()); s2.get_student_details(); }
Mutable Implementation
// Using Mutable & Borrow operator #[derive(Debug, Clone)] struct Students { id: i32, name: String, course: String, } impl Students { fn get_student_details(&self){ println!("{}", "-".repeat(50)); println!("{}", self.id); println!("{}", self.name); println!("{}", self.course); println!("{}", "-".repeat(50)); } fn create_student(pid:i32,pname:String,pcourse:String) -> Students{ Students{id:pid,name:pname,course:pcourse} } fn change_student_details(&mut self, id:i32, new_name:String, new_course:String){ self.name=new_name; self.course=new_course; } } fn main() { let s = Students { id: 10, name: String::from("Rachel"), course: String::from("DB"), }; let s1 = Students { id: 11, name: String::from("Monica"), course: String::from("DB"), }; s.get_student_details(); s1.get_student_details(); // Creating Student using Associated Function and printing it via Method let mut s2 = Students::create_student(12,"Ross".to_string(),"C++".to_string()); s2.get_student_details(); s2.change_student_details(12,"Ross Geller".to_string(),"CPP".to_string()); s2.get_student_details(); }
- The struct instance must be declared mut
- The method must take &mut self
Tuple Struct
Tuple Structs - Similar to Classic but fields have no names.
// Tuple Struct struct Coordinates(u32, u32); fn main() { let xy = Coordinates(10, 20); //it behaves like Tuples println!("Value of the Tuple Struct xy {},{}", xy.0, xy.1); //Destructuring Tuple Struct let Coordinates(a,b) = xy; println!("Values of variables a & b {},{}", a, b); }
- When meaning is positional
- When you want a distinct type but minimal syntax
- Often used for newtype pattern
Summary
- Structs group related data with named fields
- Stored on stack unless fields allocate on heap
- Move semantics apply unless Copy is implemented
- Use Clone for explicit duplication
- impl defines behavior
- Methods control ownership via self
- Tuple structs trade readability for compactness