Exercise 3
Introduction to Programming - Exercise
Prof. Dr. Christof Fetzer & Samuel Knobloch
TU Dresden
1
What we will cover
● Structs
○ Common Structs, Tuple Structs, Unit-Like Structs
● Function, Method and Associated Function
● Constructors
● Ownership - What is Self?
● Using basic Traits
○ Display, Debug, Clone
● Problems with Structs
2
Structs
3
Outline
● Basics on Structs
○ Differences with tuple
● Printing a Struct
● Working with and on structs
● Constructor
● Methods and Associated Functions
● Ownership, Borrowing and Self
4
Rev: Type tuple
● Fixed set of elements
○ Can have different types
● Access using the index of the field
○ We have to remember, what we stored at which position
{
let my_tuple = (135790, "Bob", false);
println!("{:?}", my_tuple); // Prints: (135790, “Bob”, false)
println!("{}", my_tuple.2); // Prints: false
}
5
Basics: Composed Type struct (1/3)
● Similar to tuples at first, but:
○ Way more flexible
○ We name our struct type
○ Each field has a name and a type
○ Order of parameters does not matter
● Three types:
○ Common struct
○ Tuple struct
○ Unit struct
6
Basics: Composed Type struct (2/3)
● New structs are defined using keyword struct
● Names are in CamelCase (each word starts with capital letter)
● Field names are in snake_case
● After defined, can be instantiated and filled with data
○ Data is stored in key:value format (it looks like JSON, yes)
● Field access uses dot notation: var.field
https://doc.rust-lang.org/1.0.0/style/style/naming/README.html
7
Basics: Composed Type struct (3/3)
● Let’s convert our tuple example into a struct definition
struct Student {
id: u32,
name: String,
graduated: bool
}
fn main() {
let my_struct = Student {
id: 135790,
name: String::from("Bob"),
graduated: false,
};
println!("{}", my_struct.name); // Prints: Bob
} 8
Printing a struct
● We can access single fields, but how about printing the whole struct?
● Similar to tuple, let’s try to use the built-in debug formatter with :?
{
let my_struct = Student {
id: 135790,
name: String::from("Bob"),
graduated: false
};
println!("{:?}", my_struct);
}
9
Printing a struct
error[E0277]: `Student` doesn't implement `Debug`
--> src/main.rs:13:22
|
13 | println!("{:?}", my_struct);
| ^^^^^^^^^ `Student` cannot be formatted using `{:?}`
|
= help: the trait `Debug` is not implemented for `Student`
= note: add `#[derive(Debug)]` to `Student` or manually `impl Debug for Student`
= note: this error originates in the macro `$crate::format_args_nl` which comes from the
expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more
info)
help: consider annotating `Student` with `#[derive(Debug)]`
|
1 | #[derive(Debug)]
|
10
What does this error tell us?
11
Traits Display and Debug (1/2)
● Macro println! calls the display formatter by default when reaching {}
● Trait Display:
○ Implemented for default types of the language
○ Makes the output looking nice and as expected
○ Not defined for types we created ourselves
● We don’t have a Display defined for our struct Student (yet)
○ How to do that will be covered during “Traits and Generic Types”
● So we cannot print our struct, then? ¯\_(ツ)_/¯
12
Traits Display and Debug (2/2)
● There is another Trait we can use here: Debug
○ For the formatter, we use {:?} instead of {}
● Also, we need to apply this trait onto our new struct
○ Done by adding the following macro: #[derive(Debug)]
#[derive(Debug)]
struct Student {
id: u32, Output:
name: String, Student { id: 135790, name: "Bob", graduated: false }
graduated: bool
}
13
Structs and Mutability
● To alter fields, we have to make our struct instance mutable
○ Cannot be done per field
{
let mut my_struct = Student {
id: 135790,
name: String::from("Bob"),
graduated: false
};
println!("Student name before: {}" , my_struct .name);
my_struct .name = String::from("Alice");
println!("Student name after : {}" , my_struct .name);
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6388950bea0ea686c5fd31be4cfb1d75 14
Struct update syntax
● Goal: Create several instances of our type
○ Without repeating fields that are the same
{
let mut my_struct1 = Student {
id: 135790,
name: String::from("Bob"),
graduated: false
};
let mut my_struct2 = Student {
id: 246800,
name: String::from("Bob"),
Lines are duplicates - Can we avoid this?
graduated: false
};
} 15
Struct update syntax
● Used for creating new instances with partially new data
○ When initializing, we only set the new fields and then copy the missing
○ Old instance is no longer valid!
{
let my_struct1 = Student {
id: 135790,
name: String::from("Bob"),
graduated: false
};
let my_struct2 = Student {
id: 246800, Wait: Ownership is transferred, this invalidates my_struct1
Why does this happen?
.. my_struct1
};
}
16
Problem with inheriting data
● This code will fail, when trying to print both variables
● Struct update syntax in general works for primitive types
○ Reason: Data is just copied (Integer, Float, Bool, String Literals …)
● Problem with complex types
○ Rust tries to move the data, so my_struct1 is no longer valid
● Any solution…?
○ We will come back to this problem later
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1f7821eecd8d2a922f5aba2a2d7841c2
17
Constructor: Creating new structs (1/3)
● Overhead while creating every single instance by hand
● So, let’s write a function for that
fn create_student(new_id: u32, new_name: String, new_graduated: bool) -> Student {
Student {
id: new_id,
name: new_name, Explicit assignment
graduated: new_graduated
}
}
18
Constructor: Creating new structs (2/3)
● It still looks a bit complicated, but Rust can help here
● key:value can be omitted if parameters match field names
○ Code looks much cleaner now
fn create_student(id: u32, name: String, graduated: bool) -> Student {
Student {
id,
name,
graduated
}
}
19
Constructor: Creating new structs (3/3)
● To summarize:
○ Functions producing new objects are called constructors
○ Such a function can be used to create new instances of a struct
○ No explicit assignment required if parameter names match field names
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=958127ea6eb56cc551004c40e76a398c
20
Function… or rather Method?
● Methods are like functions:
○ Are defined the same way using fn keyword
○ Can receive input parameters and return as usual
● Differences with normal functions:
○ Can only be defined in a special environment
○ Have a specific first parameter
○ Are implemented for a type and only valid in its context
21
How to define a method
in the Rust language?
22
The Implementation Block
● Environment for defining methods and associated functions
● Block begins with keyword impl
○ Followed by the type implementing for
● Multiple implementation blocks for the same type are OK
impl Student {
23
Extend our example for methods
● First, we add another type Course a student attended
● Also, we extend Student with an array of Courses and removed graduated
#[derive(Debug)]
struct Course {
name: String,
passed: bool,
}
#[derive(Debug)]
struct Student {
id: u32,
name: String,
courses: [Course; 3]
}
24
Writing our first method (1/4)
impl Student {
fn check_graduation(&self) -> bool {
for course in self.courses.iter() {
if !course.passed {
return false; Iterate over our array of courses
}
}
true
}
}
25
Writing our first method (2/4)
● Each method has a parameter called self
○ self refers to the struct for which we implement
○ Gives us access to the fields and values of the struct itself
● &self means we receive a reference and borrow
○ Otherwise, ownership is transferred and a new instance must be returned
impl Student {
fn check_graduation(&self) -> bool {
...
}
}
26
Writing our first method (3/4)
What happens in this method?
● If value of passed of one of the courses is false:
○ The student has not yet passed that course
○ Therefore, he cannot graduate
● Only if all courses are passed, we will return true
27
Writing our first method (4/4)
● Now, let’s create some courses and add them to our student
● At end the, we’ll see if Alice passed or not
fn main() {
let c1 = Course { name: String::from("INF-B-230"), passed: true };
let c2 = Course { name: String::from("INF-B-240"), passed: true };
let c3 = Course { name: String::from("INF-AQUA"), passed: true };
let my_struct = Student { name: String::from("Alice"), id: 246800, courses: [c1, c2, c3] };
println!("Checking if Alice has passed: {}", my_struct.check_graduation());
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=516cbbcc0dd34ab1a38d6bccca39d2cd
28
Using self with ownership
transfer or without it?
29
self and Ownership
● By defining self as a method parameter:
○ We transfer the ownership of the struct instance to the function
● Let’s change our method like the following
impl Student {
fn check_graduation(self) -> bool { Without &
for course in self.courses.iter() {
if !course.passed {
return false;
}
}
true
}
} 30
self and Ownership
● We also add another println! to see, what happens with my_struct
○ …you probably guessed it already…
fn main() {
...
println!("Checking if Alice has passed: {}", my_struct.check_graduation());
println!("{:?}", my_struct);
}
31
self and Ownership
error[E0382]: borrow of moved value: `my_struct`
--> src/main.rs:50:22
|
40 | let my_struct = Student {
| --------- move occurs because `markus` has type `Student`, which does not implement the `Copy` trait
...
48 | println!("Checking if Alice has passed: {}", my_struct.check_graduation());
| ------------------ `my_struct` moved due to this method
call
50 | println!("{:?}", my_struct);
| ^^^^^^^^^ value borrowed here after move
|
note: this function takes ownership of the receiver `self`, which moves `my_struct`
--> src/main.rs:17:25
|
17 | fn check_graduation(self) -> bool {
| ^^^^
32
What is self exactly?
33
Explaining self (1/4)
● To get a better understanding of self, let’s create another method
○ This time, for Course
● Note: We use another option for “pretty printing” - {:#?}
impl Course {
fn print(&self) {
println!("In method: {:#?}", self);
}
Question: What is behind self?
}
https://doc.rust-lang.org/std/fmt/#sign0
34
Explaining self (2/4)
● In main, we create an instance of Course and pretty print it
fn main() {
let c1 = Course {
name: String::from("INF-B-230"), Output:
passed: true,
In main: Course {
}; name: "INF-B-230",
passed: true,
}
println!("In main: {:#?}", c1);
}
35
Explaining self (3/4)
● Now, we adjust main again and call the method defined on Course
fn main() {
let c1 = Course {
name: String::from("INF-B-230"),
passed: true,
};
println!("In main: {:#?}", c1);
c1.print();
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c9a39c6089400f7cff0376249e3b7fd2
36
Explaining self (4/4)
● Result: Course outputs are identical
○ Using self, we access the same data of the same instance
From main: Course {
name: "INF-B-230",
passed: true,
}
From method: Course {
name: "INF-B-230",
passed: true,
}
37
Changing the content of
a struct using methods
38
Methods: Updating structs (1/3)
● What we had so far:
○ Update field values using direct access with dot operator
● The cleaner way:
○ Writing a method that updates the field for us
■ Can be extended with pre-checks and other things necessary
● In order to write data, we need a mutable reference
○ First parameter now is changed to &mut self
39
Methods: Updating structs (2/3)
● As a start, we add the ability to update the Course status
○ As a convention, setter functions should be prefixed with “set_”
■ Getter functions usually have the name of the field
impl Course {
fn set_passed(&mut self, passed: bool) {
self.passed = passed;
}
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6e9127f04d62f442993f77a049e981d5
40
Methods: Updating structs (3/3)
● c1 is initialized with “passed: true”
● After calling set_passed, it should update
fn main() {
let mut c1 = Course {
Output:
name: String::from("INF-B-230"),
Course after set_passed: Course {
passed: true, name: "INF-B-230",
}; passed: false,
}
c1.set_passed(false);
c1.print();
println!("Course after set_passed: {:#?}", c1);
}
41
Why using methods?
● Helping our program to be better structured
● Methods related to a data structure are linked to itself
● Maintenance and development of the program becomes easier
○ Activities like writing end-user documentation get easier
● Effectively creating a special namespace
○ For methods and functions specific to a struct
42
We mentioned another
type earlier. So, what is
an Associated Function?
43
Associated Functions
● Unlike methods, they do not work on an instance of the struct
● No self parameter, they look like a common function
● They are related to a specific struct
○ Can only be called using the namespace of the struct
● Most common use: Create an instance of a struct
○ In String::from, the function from is related to the String struct
■ Returns an instance of String
https://doc.rust-lang.org/std/string/struct.String.html
44
Method vs Associated Function
● Methods are a kind of associated function that
○ Let you specify functionality instances of a struct have
○ Called using dot operator .
○ Are called on a particular instance of a type
● Associated functions are
○ Defined on a type generally
○ Called using namespace operator ::
○ Often used for constructors returning new instances of the struct
■ Rev: Struct update syntax
45
Constructor as Associated Function (1/2)
● Earlier, we introduced create_student
○ Creates new Student structs for us
● Let’s transform this function into an associated one
○ Another convention here, constructors should be named new(...)
impl Student {
fn new(id: u32, name: String, courses: [Course; 3]) -> Self {
Self { id, name, courses }
}
} Self is equivalent to Student
● Self always refers to the type we are implementing on
46
Constructor as Associated Function (2/2)
● Adjusting our main as well
○ We now use our newly created associated function Student::new()
fn main() {
let c1 = Course { name: String::from("INF-B-230"), passed: true };
let c2 = Course { name: String::from("INF-B-240"), passed: true };
let c3 = Course { name: String::from("INF-AQUA"), passed: true };
let my_struct = Student::new(246800, String::from("Alice"), [c1, c2, c3]);
println!("{:#?}", my_struct);
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=38ae76cbfa9f648384f9f3e9957978de
47
Problem with inheriting data (1/3)
● With all the knowledge we have now, let’s come back to this problem
○ The compiler will give us the following error:
error[E0382]: borrow of partially moved value: `my_struct1`
--> main.rs:21:23
|
16 | let my_struct2 = Student {
| ---------------------
17 | | id: 246800,
18 | | ..my_struct1
19 | | };
| ----- value partially moved here
20 |
21 | println!("{:#?}", my_struct1);
| ^^^^^^^^^^ value borrowed here after partial move
|
= note: partial move occurs because `my_struct1.name` has type `String`, which does not implement the `Copy`
trait
error: aborting due to previous error
48
Problem with inheriting data (2/3)
● Our goal is to keep both variables my_struct1 / my_struct2
○ But: We ran into an Ownership problem
● What we know so far about String (and other complexer types)
○ They can be duplicated using method clone()
● So, where does this clone() method come from?
○ And how can we implement it for our own type Student?
49
Trait Clone
● As known, some types can be implicitly copied
○ These simple data types do not require heap allocation
● Cannot be safely done for the rest of the data types
● So, we have to clone them
● Clone accurately copies the data values
● To make a struct cloneable, we need to
○ Add the trait Clone to the derive macro we already have with Debug
○ Call the method on the instance of our struct
50
Problem with inheriting data (3/3)
● So, we extend our code now with the new trait
#[derive(Debug, Clone)]
struct Course {
name: String,
passed: bool,
}
#[derive(Debug, Clone)]
struct Student {
id: u32,
name: String,
courses: [Course; 3],
}
51
Problem with inheriting data (3/3)
● In main, we now add the new method call
{
...
let my_struct2 = Student {
...
..my_struct1.clone() // We added “.clone()” The ownership of the cloned value
}; is transferred
println!("{:#?}", my_struct1);
println!("{:#?}", my_struct2);
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9e29da2895459f0df8ef9e7c4ee7826c
52
We listed two other
types of structs, let’s
have a look at them.
53
Tuple Structs
54
Tuple Structs
● The most important features of structs vs tuples:
○ Their values have names
○ No need to maintain the order of the data
○ Access the data using keys
● Sometimes, this full functionality is not required, but
○ We want to store data the clean way
○ Have a named type to rely on
55
Tuple Structs
● Definition works without key: value
○ We use the tuple notation
○ Fields do not have names
● Accessing the data again uses dot operator, followed by the index
#[derive(Debug)]
struct TupleStruct(u8, u8, u8);
fn main() {
let mut tuple = TupleStruct(0, 0, 0);
tuple.0 = 255; // Updating first index with “dot”
println!("{:?}", tuple); // Prints “TupleStruct(255, 0, 0)
}
56
Tuple Structs: Example
● Simple use-cases for tuple structs are e.g.
○ Storing color values like RGB, CMYK
○ Storing an IP address
#[derive(Debug)]
struct RGB(u8, u8, u8);
#[derive(Debug)]
struct IPv4(u8, u8, u8, u8);
fn main() {
let red = RGB(255, 0, 0);
let local_ip = IPv4(127, 0, 0, 1);
}
57
Tuple Structs: Summary
● Use this type in bigger programs with many tuples
○ And instead of tuples, if suitable
● Probability of error is much less with type separation
● Can be extended with methods as well using impl
○ This is not possible for simple tuples
58
Unit-Like Structs
59
Type Unit
● () is also called unit
● A type having only one value which is ()
● When to use Unit?
○ No other meaningful value to return
● Functions having no return value defined, implicitly return ()
https://doc.rust-lang.org/std/primitive.unit.html
60
Unit-Like Structs
● We can define structs to act like a unit
○ Structs that do not have any fields, so not storing any data
● We can use Unit-Like structs to define (e.g.) data-less types
#[derive(Debug)]
struct UnitLikeStruct;
fn main() {
let my_unit = UnitLikeStruct;
let same_unit_as_my_unit = UnitLikeStruct {};
println!(
"my_unit: {:?}, same_unit_as_my_unit: {:?}",
my_unit, same_unit_as_my_unit
);
}
61
Problems with Structs
62
Problems with Structs
● Let’s have a look at our example
#[derive(Debug, Clone)] fn main() {
struct Course { let course: Course;
name: String, course = Course {
passed: bool, name: String::from("INF-AQUA"),
lecturer: Lecturer passed: false,
} lecturer: Lecturer {
#[derive(Debug, Clone)] name: String::from("Bob"),
struct Lecturer { course
name: String, }
course: Course };
} }
63
Problems with Structs
● Recursion, sometimes a big problem
for programmers
● Simple example:
○ Two structs, each of which has a field
of the other
○ That means we would need infinite memory
■ Can we solve this now?
64
Problems with Structs
error[E0072]: recursive types `Course` and `Lecturer` have infinite size
--> main.rs:2:1
|
2 | struct Course {
| ^^^^^^^^^^^^^
...
5 | lecturer: Lecturer}
| -------- recursive without indirection
7 | #[derive(Debug, Clone)]
8 | struct Lecturer {
| ^^^^^^^^^^^^^^^
9 | name: String,
10 | course: Course
| ------ recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
5 ~ lecturer: Box<Lecturer>
6 | }
...
9 | name: String,
10 ~ course: Box<Course>
|
error: aborting due to previous error
65
Problems with Structs
● The error we see refers to so called Recursive Types
● At compile time, we cannot say how much memory is required
● For now, this problem cannot be solved
○ So, avoid creating such a recursive problem with our structs
○ We don’t know yet how dynamic memory in Rust works
66
Overall Summary
● Structs are a powerful type with unique name
○ Used for managing and organizing our code
○ Our fields have unique names for accessing data
● Using impl, we can define methods and associated functions for a struct
● Ownership and self are working together very well
● Traits are helpful extensions we can derive on our structs
○ Debug, Clone are the most common
67
Feedback
● Feedback? Today we’ll use AMCS platform
● Use this pin: RST2023
68