## DEV Community

Serokell

Posted on • Originally published at serokell.io on

# Get Started With Rust: Structs

In almost any programming language, you can create data structures – like classes or records – that pack a group of things together. Rust is no exception to this with structs.

• how to define and instantiate a struct;
• how to derive a trait for a struct;
• what are struct methods, and how to use them.

All the code from this post is available here.

## What is a struct in Rust?

In Rust, a struct is a custom data type that holds multiple related values.

Let’s jump straight into our first example and define `Point` – a wrapper for two coordinates.

``````// [1] [2]
struct Point {
// [3] [4]
x: i32,
y: i32,
}

// We
// [1]: tell Rust that we want to define a new struct
// [2]: named "Point"
// [3]: that contains two fields with names "x" and "y",
// [4]: both of type "i32".

``````

### Tuple structs

You can use a tuple struct to group multiple values together without naming them.

Tuple structs are defined like this:

``````// [1] [2] [3]
struct Point(i32, i32);

// [1]: Struct name.
// [2]: First component / field of type i32.
// [3]: Second component of type i32.

``````

### How to create an instance of a struct?

Here are the structs that we defined previously.

``````// Ordinary struct
struct Point {
x: i32,
y: i32,
}

// Tuple struct
struct PointTuple(i32, i32);

``````

We can now create instances of these structs – objects of type `Point` and `PointTuple`.

``````// [1]
let p_named = Point {
// [2] [3]
x: 13,
y: 37,
};

// [1]: Struct name.
// [2]: Field name.
// [3]: Field value.

// [1] [2] [3]
let p_unnamed = PointTuple(13, 37);

// [1]: Struct name.
// [2], [3]: Field values.

``````

As you can see, the initialization syntax is pretty straightforward. It’s like defining a JSON object where keys are replaced by field names.

Two things to keep in mind:

• All fields must have values. There are no default parameters in Rust.
• The order of field-value pairs doesn’t matter. You can write `let p_strange_order = Point { y: 37, x: 13 };` if you wish to.

### How to access struct fields?

#### Getting a value

You can get the value of a field by querying it via dot notation.

``````let x = p_named.x;
let y = p_named.y;

``````

One may wonder: what’s the syntax to get the value of a specific field of a tuple struct? Fortunately, Rust has chosen the simplest way possible, by indexing tuple components starting from zero.

``````let x = p_unnamed.0;
let y = p_unnamed.1;

``````

#### Setting a value

It’s possible to change the value of a field using dot notation, but the struct variable must be defined as mutable.

``````let mut p = PointTuple(1, 2);
// ^^^
p.0 = 1000;
assert_eq!(p.0, 1000);

``````

Unlike some other languages, Rust doesn’t allow to mark a specific field of a struct as mutable or immutable.

``````struct Struct {
mutable: mut i32,
// ^^^
// Expected type, found keyword `mut`.
immutable: bool,
}

``````

If you have an immutable binding to a struct, you cannot change any of its fields. If you have a mutable binding, you can set whichever field you want.

### Reducing boilerplate

Two features reduce boilerplate code while initializing struct fields:

• struct update syntax
• field init shorthand

To illustrate them, we first need to define a new struct and create an instance of it using the usual syntax.

``````struct Bicycle {
brand: String,
kind: String,
size: u16,
suspension: bool,
}

let b1 = Bicycle {
brand: String::from("Brand A"),
kind: String::from("Mtb"),
size: 56,
suspension: true,
};

``````

#### Struct update syntax

It often happens that you want to copy an instance of a struct and modify some (but not all) of its values. Imagine that a different bicycle brand manufactures a model with identical parameters, and we need to create an instance of that model.

We can move all values manually:

``````let b2 = Bicycle {
brand: String::from("Other brand"),
kind: b1.kind,
size: b1.size,
suspension: b1.suspension,
};

``````

But this method involves too much code. Struct update syntax can help:

``````let b2 = Bicycle {
brand: String::from("Other brand"),
..b1
// ^^ struct update syntax
};

``````

`..b1` tells Rust that we want to move the remaining `b2`’s fields from `b1`.

#### Field init shorthand

You can omit the field name in `field: value` initialization syntax if the value is a variable (or function argument) with a name that matches the field.

``````fn new_bicycle(brand: String, kind: String) -> Bicycle {
Bicycle {
brand,
// ^^^ instead of "brand: brand"
kind,
// ^^^ instead of "kind: kind"
size: 54,
suspension: false,
}
}

``````

## Traits

Okay, let’s try to do something with our struct. For example, print it.

``````let p = Point { x: 0, y: 1 };
println!("{}", p);
// ^^^
// Compile error

``````

Suddenly, the compiler says `"Point" doesn't implement "std::fmt::Display"` and suggests that we use `{:?}` instead of `{}`.

Ok, why not?

``````let p = Point { x: 0, y: 1 };
println!("{:?}", p);
// ^^^
// Compile error

``````

Unfortunately, that doesn’t help. We still get the error message: `"Point" doesn't implement "Debug"`.

But we also get a helpful note:

``````note: add `#[derive(Debug)]` to `Point` or manually `impl Debug for Point`

``````

The reason for this error is that both `Debug` and `std::fmt::Display` are traits. And we need to implement them for `Point` to print out the struct.

### What is a trait?

A trait is similar to an interface in Java or a typeclass in Haskell. It defines certain functionality that we might expect from a given type.

Usually, traits declare a list of methods that can be called on the types that implement this trait.

Here’s a list of commonly-used traits from the standard library:

In our example, `Point` needs an implementation of the `Debug` trait because it’s required by the `println!()` macro.

There are two ways to implement a trait.

• Derive the implementation. The compiler is capable of providing basic implementations for a fixed list of traits via the `#[derive]` macro.
• Manually write the implementation.

Let’s start with the first option. We need to add `#[derive]` before the definition of `Point` and list the traits we want to derive.

``````#[derive(Debug, PartialEq, Eq)]
struct Point {
x: i32,
y: i32,
}

let p = Point { x: 0, y: 1 };
println!("{:?}", p);

``````

Now the compiler doesn’t complain, so let’s run our program.

``````> cargo run
Point { x: 0, y: 1 }

``````

We didn’t write anything related to formatting `Point` ourselves, but we already have a decent-looking output. Neat!

#### `Eq` and `PartialEq`

You may have noticed that we also derived the `Eq` and `PartialEq` traits.

Because of that, we can compare two `Point`s for equality:

``````let a = Point { x: 25, y: 50 };
let b = Point { x: 100, y: 100 };
let c = Point { x: 25, y: 50 };

assert!(a == c);
assert!(a != b);
assert!(b != c);

``````

### Manually implementing traits

• Only a small number of traits are derivable. Though, procedural macros allow for creation of custom `derive` attributes.
• Sometimes the derived implementation doesn’t match your needs.

That’s why it’s possible to implement a trait by yourself.

#### Implementing `Display`

When we tried to print a `Point` instance using `"{}"`, the compiler said that `Point` doesn’t implement `std::fmt::Display`. This trait is similar to `std::fmt::Debug` but it has a few differences:

• It must be implemented manually.
• It should format values in a prettier way, without containing any unnecessary information.

You can think of the `Debug` and the `Display` as the formatters for programmers and users, respectively.

Let’s add a manual implementation of `Display`. In the brackets, we have to define every function that is declared in the trait. In our case, there is only one — `fmt`.

``````impl std::fmt::Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

``````

Now we can print our `Point` struct both for developers and users.

``````let p = Point::new(0, 1);

assert_eq!(format!("{:?}", p), "Point { x: 0, y: 0 }");
assert_eq!(format!("{}", p), "(0, 0)");

``````

## Struct methods

Just like in Java or C++, you can define methods that are associated with a particular type. This is done in an `impl` block.

``````impl Point {
// Method that can't modify the struct instance
fn has_same_x(&self, other: &Self) -> bool {
self.x == other.x
}

// Method that can modify the struct instance
fn shift_right(&mut self, dx: i32) {
self.x += dx;
}
}

``````

You can call these methods via dot notation.

``````// Mutable binding
let mut point = Point { x: 25, y: 25 };
assert!(!point.has_same_x(&Point { x: 30, y: 25 }));

// Calling a method that changes the object state
point.shift_right(3);
assert_eq!(point.x, 28);

``````

Methods can mutate the struct instance they are associated with, but this requires `&mut self` as the first argument. This way, they can be called only via mutable binding.

### Self

You may have noticed that methods have `self` as their first argument. It represents the instance of the struct the method is being called on, similar to how it’s done in Python. Some syntax sugar is involved here. Let’s desugar it in two steps.

1 `fn has_same_x(&self, other: &Self)` `fn shift_right(&mut self, dx: i32)`
2 `fn has_same_x(self: &Self, other: &Self)` `fn shift_right(self: &mut Self, dx: i32)`
3 `fn has_same_x(self: &Point, other: &Point)` `fn shift_right(self: &mut Point, dx: i32)`

Don’t confuse `self` with `Self`. The latter is an alias for the type of the `impl` block. In our case, it’s `Point`.

### Associated functions

You can also use the `impl` block to define functions that don’t take an instance of `Self`, like static functions in Java. They are commonly used for constructing new instances of the type.

``````impl Point {
// Associated function that is not a method
fn new(x: i32, y: i32) -> Point {
Point { x, y }
}
}

``````

You can call these functions by using a double semicolon (`::`):

``````let point = Point::new(1, 2);

``````

### Why use methods instead of regular functions?

Methods are quite useful for making your code better. Let’s look at some examples.

#### Nice-looking dot notation

To call methods of a type, we use dot notation.

Besides beautiful syntax, dot notation provides an additional property — autoreferencing — which means you can write `point.shift_right(3);` instead of `(&mut point).shift_right(3);`.

Functions don’t have this advantage, you always have to reference:

``````shift_right(&mut point, 3);

``````

#### Code organization

All methods are placed inside one or multiple `impl` blocks. This implies two things.

First, you don’t have to import methods.

``````use my_module::MyStruct;

...
let s = MyStruct::new();
s.do_stuff();
s.do_other_stuff();

``````

Without methods, it would look like this:

``````use my_module::{MyStruct, do_stuff, do_other_stuff}; // ugly

...
let s = MyStruct::new();
do_stuff(&s);
do_other_stuff(&s);

``````

Second, methods are namespaced: you don’t have name collisions across types.

``````my_rectangle.area()
my_circle.area()

``````

Without methods, you would need to write something like this:

``````rectangle_area(my_rectangle)
circle_area(my_circle)

``````

## Methods vs. traits

Methods and traits are somewhat similar, so beginners can mix them up.

The general rule for when to use which is simple for beginners:

• If you are implementing a common functionality for which a trait exists (converting to string, comparison, etc.), try to implement that trait.
• If you are doing something specific to your application, probably use methods in regular `impl` blocks.

A method can be invoked only on the type it is defined for. Traits, on the other hand, overcome this limitation, as they are usually meant to be implemented by multiple different types.

So traits allow certain functions to be generalized. These functions don’t take just one type, but a set of types that is constrained by a trait.

We have already seen such examples:

• `println!("{:?}", ...)` doesn’t care what kind of an object we want to print as long as it implements the `Debug` trait.
• `assert_eq!(...)` allows to compare objects of any type as long as they implement `PartialEq`.

## Conclusion

In this article, we covered the basics of structs in Rust. We explored the ways of defining, initializing, and adding implementation blocks to both structs and tuple structs. We also looked at traits and how to derive or implement them.

Structs are not the only way to create custom types. Rust also has enums. While we did not cover them in this blog post, concepts as methods, traits, and deriving can be applied to them in a similar way.