13 Defining Struct(ure)s
Create new struct
types and destructure them. Additionally, add behavior through derive
-macros.
You can define a type as being a collection of other types by using the struct
keyword.
struct Person {
: String,
name: i32
age}
struct
s are named using PascalCase, as opposed to fn
s which are named using snake_case convention. Structs are constructed using a “literal” syntax. Where we write the name of the struct followed by curlys and the fields like so:
let me = Person { name: "Josiah".to_string(), age: 29 };
By default, new structs do not get any special behavior. Meaning they cannot be printed using println!()
:
struct Person {
: String,
name: i32
age}
fn main() {
let me = Person { name: "Josiah".to_string(), age: 29 };
println!("{me:?}");
}
error[E0277]: `Person` doesn't implement `Debug`
--> src/main.rs:8:15
|
8 | println!("{me:?}");
| ^^^^^^ `Person` cannot be formatted using `{:?}`
|
= help: the trait `Debug` is not implemented for `Person`
= note: add `#[derive(Debug)]` to `Person` or manually `impl Debug for Person`
= 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 `Person` with `#[derive(Debug)]`
|
1 + #[derive(Debug)]
2 | struct Person {
Deriving behavior
Rust contains a special type called a trait. They’re akin to S3 generics. They are a a collection of methods that can be implemented by foreign types.
In this case, Debug
is a trait that allows for debug printing using println!()
. Our new type does not implement this by default. However, we can derive this behavior because everything inside of the struct does.
To derive behavior we use the #[derive()]
attribute.
#[derive(Debug, Clone)]
struct Person {
: String,
name: i32
age}
The Debug
trait allows debug printing of the type Person
via dbg!()
or println!("{p:?})
.
fn main() {
let me = Person { name: "Josiah".to_string(), age: 29 };
println!("{me:?}");
}
Person { name: "Josiah", age: 29 }
Accessing Fields
Fields of a struct
can be accessed directly, or by reference. Field can be accessed using var.field_name
.
If a field is accessed and moved, the struct
cannot be moved. The same rules of borrowing apply to a struct. You can borrow a field as much as you want but you can only move it once. Otherwise, the entire struct cannot be used.
fn main() {
let me = Person { name: "Josiah".to_string(), age: 29 };
let name = me.name;
println!("{me:?}");
}
: borrow of partially moved value: `me`
error[E0382]--> src/main.rs:10:15
|
9 | let name = me.name;
| ------- value partially moved here
10 | println!("{me:?}");
| ^^^^^^ value borrowed here after partial move
Destructuring
It is possible to destructure a struct during assignment creating many variables at one time.
To destructure during assignment you use the syntax let StructName { field, field } = variable;
fn main() {
let me = Person { name: "Josiah".to_string(), age: 29 };
let Person { name, age } = me;
println!("{name} is {age} years old");
}
Exercise
- Define a struct called
Point
which has two fieldsx
, andy
that aref64
- Derive
Debug
andClone
- In
main()
create a newPoint
struct - Debug print the new point
- Destructure the point into x and y
View solution
#[derive(Debug, Clone)]
struct Point {
: f64,
x: f64
y}
fn main() {
let point = Point { x: 3.0, y: 0.14 };
println!("The point is {:?}");
let Point { x, y } = point;
}