The Structs in Rust

How to create custom types in Rust using Structs

When we came from POO languages to Rust language, one of the first questions is:

"How can I define a class here?"

It doesn't take long until we discover that the language doesn't have classes like most others. But maybe we're asking the wrong question. After all, we want to know how to put some data together, give them a name and give them some behaviors too.

So, the right question is:

"How can I create a custom type to join some data and perform some methods?"

And the answer is by creating structures.

What is a Struct?

It is a way to put some data together with a predefined shape, just like a class. So, imagine we want to represent a product that has a name, price, and quantity. We can do it that way:

struct Product {
  name: String,
  price: f32,
  quantity: i32,
}

Basically, in the code above, the struct Product has three properties: a string called name, a float number called price, and an integer number called quantity.

How to "instance" a product?

In the Rust programming language, there is no new keyword to instance objects from some class. But it's pretty easy to create an instance of some struct in rust. Just like that:

fn main() {
  let webcam = Product {
    name: String::from("Webcam"),
    price: 49.99,
    quantity: 100
  };

  println!("{} - ${} - {}", webcam.name, webcam.price, webcam.quantity);
  // output: Webcam - $49.99 - 100
}

In the snippet code above, we have created an instance of Product e gave it the name "Webcam", the price 49.99 and the quantity 100.

What about the methods?

Maybe right now you are thinking:

"Ok, Gabriel. But, there's a way to define methods like in the classes?"

And the answer is: Yes!. You might be surprised when you see it at first, but if you're willing to explore new ways of programming, you might find it quite elegant.

So, let's define three methods:

  • value: Returns the potential return on sales;
  • resume: Prints the resume on the screen.
impl Product {
  fn value(&self) -> f32 {
    self.quantity as f32 * self.price
  }

  fn resume(&self) -> () {
      println!("{} - ${} - {}", self.name, self.price, self.quantity);
  }
}

First, you can notice that all the methods receive as the argument number one a self keyword, that is a reference to the instance.

The arrow in the method's signature points to the return type, which is a float number in the value case and void/empty in the resume case.

And finally, in the value method we return the conversion of quantity to float multiplied for the product price. In the resume method, we just print some information on the screen using the macro println! accessing the self properties.

Immutability vs. Mutability

By default, a variable in Rust can not be changed and this includes instances from structs. Codes like the below receives an error when compiling:

fn main() {
  let webcam = Product {
    name: String::from("Webcam"),
    price: 49.99,
    quantity: 100
  };

  webcam.name = String::from("Webcam - Logi");
}

Rust will always give preference to functional programming and, by consequence, immutability. But you can set a variable as a mutable one. Like this one:

fn main() {
  let mut webcam = Product {
    name: String::from("Webcam"),
    price: 49.99,
    quantity: 100
  };

  webcam.name = String::from("Webcam - Logi");
}

But, if instead, you prefer the functional approach, you can make copies and change only what you want to change. You just need to use the struct update syntax.

fn main() {
  let webcam = Product {
    name: String::from("Webcam"),
    price: 49.99,
    quantity: 100
  };

  let update = Product {
    name: String::from("Webcam - Logi"),
    ..webcam
  };
}

Thanks for reading!

I'm a Brazilian guy and I am actually learning the Rust programming language and you can feel free to make corrections (even corrections in English :D)

Gabriel Rufino

Did you find this article valuable?

Support Gabriel Rufino by becoming a sponsor. Any amount is appreciated!