The Basics of Rust Structs

Pretty structs, as far as the eye can see...

Pretty structs, as far as the eye can see…

structs are one of the basic building blocks in Rust. A struct lets us create more complex data types of our own. Instead of having to jump through naming hoops to make our own data structures, we can create whatever we want.

Our First Rust struct

Let’s look at creating a simple struct for a to do application. Why a todo application? Because it’s simple and I’m writing this, not you.

Our struct will look something like this:

pub struct Todo {
    title: String,
    description: String, 
    // We're using seconds since epoch to avoid bringing in additional
    // dependencies on an external library and making this more complex than
    // it needs to be.
    created_at_s: i64,
    completed_at_s: i64
}

This is a simple struct. The Todo describes a single item on a todo list – it has a name, a description, and timestamps to store when something was created and completed. Those last two are for tracking how lazy we are.

We’ve made the struct public with the pub modifier since we might want to use it in a different module of our program.

struct Field Visibility in Rust

There’s a problem with our struct – by default struct fields are private. Other parts of our program won’t be able to read the title or description, they’ll just know that we’ve got a pile of information. We need to fix that!

pub struct Todo {
    pub title: String,
    pub description: String, 
    pub created_at_s: i64,
    pub completed_at_s: i64
}

Now people can read everything inside the struct! That’s probably not the best idea, though, because it’s coupling the way we store time (in seconds since January 1, 1970) with the way users work with that time. What if we decide we need more precision in the future? Or if we want to change the beginning of our epoch? People using our Todo would have to go through and change all of their code. What if those poor fools didn’t know that we meant seconds since January 1, 1970 in the Pacific time zone instead of UTC, as is the norm?

EVERYTHING IS HORRIBLE!

Thankfully, we can fix this. We’ll leave off the pub modifier from the two time-like fields. Our Todo now looks like:

pub struct Todo {
    pub title: String,
    pub description: String, 
    created_at_s: i64,
    completed_at_s: i64
}

But how will people read the time?

Adding Methods to a struct

We need to give our Todo some data access methods to make life easier for people working with it. To create methods for the struct, we have to implement them. To do that, we add an impl block that references the name of the struct:

impl Todo {
    fn created_at(&self) -> Tm {
        time::at(Timespec::new(self.created_at, 0))
    }
    
    fn completed_at(&self) -> Tm {
        time::at(Timespec::new(self.completed_at, 0))
    }
}

Wait a minute… What if we haven’t completed a Todo? Right now, we need to have a value present in the completed_at field. Rust doesn’t allow for null values, so we need some way to indicate that there may or may not be something present. From Error Handling in Rust, you might recall that we can use Option to represent the possibility of nothing.

Back to the struct we go:

pub struct Todo {
    pub title: String,
    pub description: String, 
    created_at_s: i64,
    completed_at_s: Option<i64>
}

Now we can re-write the completed_at method:

fn completed_at(&self) -> Option<Tm> {
    match self.completed_at_s {
        None => None,
        Some(t) => Some(time::at(Timespec::new(t, 0))),
    }
}

Hooray! Now we can see what the time of completion is or get back a None if the task hasn’t been completed yet.

We can even make this more concise and get rid of the match altogether:

fn completed_at(&self) -> Option<Tm> {
    self.completed_at_s.map(|t| time::at(Timespec::new(t,0)))
}

Creating Our struct

Rust makes it easy to construct a new struct using initializer syntax:

let t = Todo { title: "hooray".to_string(),
               description: "do stuff".to_string(),
               created_at_s: 0,
               completed_at_s: None };

That’s ugly; it also looks a lot like JavaScript’s initializer syntax. We don’t want to have to type all of that out every time we make a new Todo. Plus, this lets someone write the data inside the Todo however they want. We don’t want that to happen at all! Let’s add a function to make a new Todo. Inside the impl we’ll add this:

pub fn new(title: String, description: String) -> Todo {
    Todo { title: title,
           description: description,
           created_at_s: time::now_utc().to_timespec().sec,
           completed_at_s: None }
}

Now when we want to create a new Todo struct we can just execute let t = Todo::new(my_title, my_description); and we’ll get the a brand new chunk of data with the creation timestamp set to the current moment in UTC time.

Where is our struct now?

At this point, we’ve got a lot of code that we could use. Check it out:

If we executed the code, we should see something like:

Title: hooray
Description: do stuff
Created at: 1460394846
Created at (pretty): Mon, 11 Apr 2016 10:14:06

We’ve created a basic struct for a Todo application. We’ve added functions that can manipulate that struct. We’ve even implemented a basic example of how to work with the struct.

Working with basic data structures in Rust is simple and easy. We create our data structure as a struct. If we want to do work on the data, we can use impl implement functions that will manipulate the data directly. To read more about how to work with structs in Rust, check out the Rust book’s sections on Structs, Method Syntax, and the language reference about implementations.

10 Comments. Leave new

  • Regarding your is_done() method:

    This is better done with Rust’s Option.is_some(). The code would look like this

    pub fn is_done(&self) -> bool {
        self.completed_at_s.is_some()
    }
    Reply
  • Hey man, just to say thanks for your awesome explanation.
    Implementing a struct with a function that returns the struct itself as a result was the trick I needed to learn in order to move on on my studies on Rust.

    Reply
  • Rob Richards
    2017-01-26 21:51

    how do you put non-primitives in the struct like say one of these?
    https://angrylawyer.github.io/rust-sdl2/sdl2/render/struct.Renderer.html

    Reply
    • Like this:

      pub struct RendererBuilder {
          window: Window,
          index: Option,
          renderer_flags: u32
      }
      
      Reply
      • Rob Richards
        2017-01-27 13:37

        oh ok. So you wouldn’t put the sdl2::render::Renderer struct inside RendererBuilder? How would one reference the renderer after it has been initialized if it’s not stored as one of the members of your RendererBuilder struct? Sorry if these are boneheaded questions… I’m coming from OOP land and trying to figure out the best way to setup my datastructures in Rust when I would have normally made a class in java or c++. I know I could declare a variable and set it to be a sdl2::render::Renderer and use it within my main class, but that seems like a poor coding practice to me.. If its not and I need to realign my thoughts on design patterns when working with Rust then so be it. I don’t want to be confined to my own pre-conceived notions of how to setup these datastructures .. I genuinely would like to know the “proper” Rust way even if it looks backwards to me. Thanks for you help!

        Reply
        • Nah, those are not boneheaded questions at all. The best places to go for answers (well, the fastest places to go) are to either hit up http://users.rust-lang.org or to go to the #rust-beginners IRC channel.

          They’ll be able to give you a better answer than I could – it’s been a while since I played with Rust and my involvement was a lot of play.

          Reply
  • Edmund Cape
    2019-05-23 07:36

    Thank you!

    Reply
  • Leave a Reply

    Your email address will not be published. Required fields are marked *

    Fill out this field
    Fill out this field
    Please enter a valid email address.

    This site uses Akismet to reduce spam. Learn how your comment data is processed.

    Menu