0% found this document useful (0 votes)
14 views68 pages

03-Exercise Mediainfo Studies

The document discusses structs in Rust, including defining structs, printing structs, mutable structs, struct update syntax, constructors, methods, and traits like Display and Debug. It provides examples and explanations of key concepts related to working with structs.

Uploaded by

ddajvye
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views68 pages

03-Exercise Mediainfo Studies

The document discusses structs in Rust, including defining structs, printing structs, mutable structs, struct update syntax, constructors, methods, and traits like Display and Debug. It provides examples and explanations of key concepts related to working with structs.

Uploaded by

ddajvye
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 68

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

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy