Structs hold related data of different types together and have a name:
// Named fields struct
struct User {
username: String,
email: String,
credits: u64,
}
// Tuple struct
struct Point(i32, i32, i32);
// Unit struct
struct Empty;
Because Rust has no zero or nullish values, structs need to be instantiated with all their fields filled:
fn main() {
let user1 = User {
username: "xxx_BILLY_xxx".to_string(),
email: "[email protected]".to_string(),
credits: 0,
};
}
A structure needs to be marked as mut
to allow updating values:
fn main() {
let mut user1 = User {
username: "xxx_BILLY_xxx".to_string(),
email: "[email protected]".to_string(),
credits: 0,
};
user1.email = "[email protected]".to_string();
}
Fields with the same name as an existing binding in the current scope can use the shorthand syntax for field assignment:
fn main() {
let username = "xxx_BILLY_xxx".to_string();
let user1 = User {
username, // username: username
email: "[email protected]".to_string(),
credits: 0,
};
}
Creating new structures from existing ones can be done using destructuring with
the ..
syntax:
fn main() {
// ...
let user2 = User {
username: "_____samo_____".to_string(),
..user1
};
}
Methods on structs are defined using the impl
block and can reference
themselves using self
as the first parameter:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
Ownership rules apply for the self
parameter, so it can be specified as an
immutable reference &self
(most common), mutable reference &mut self
or take
the ownership of the value when specified as self
.
A special type Self
is available in method definition that refers to the type
the method is defined on:
struct Rectangle {
x: i32,
y: i32,
}
impl Rectangle {
fn square(side: i32) -> Self {
Self { x: side, y: side };
}
}
The self
parameter notation is just a syntax sugar. The explicit forms look
like this:
self
isself: Self
&self
isself: &Self
&mut self
isself: &mut Self
Rust's automatic referencing and dereferencing feature allows uniform syntax
when calling methods on types regardless of the type of self
and the value. It
adds &
, &mut
or *
on the value before the method call as needed:
struct Point(i32, i32, i32);
impl Point {
fn det(&self, other: &Point) -> i32 {
self.0 * other.0 + self.1 * other.1 + self.2 * other.2
}
}
fn main() {
let p1 = Point(13, 37, 7);
let p2 = Point(4, 2, 0);
p1.det(&p2);
// turns into
(&p1).det(&p2);
}
Associated functions are defined in an impl
block and don't use the self
parameter. They are accessed on the structure name using ::
:
struct Point(i32, i32);
impl Point {
fn new() -> Self {
Point(0, 0)
}
}
fn main() {
let p = Point::new();
}
The fully qualified syntax for function calls of a type is:
Type::function(receiver_if_method, args..)
fn main() {
let mut v = vec![1, 2, 3];
v.push(4);
// same as
Vec::push(&mut v, 4);
}
Methods called with the full syntax allows the method to be used as a function pointer.