The Basics of Rust Structs
[caption id=“attachment_1026” align=“alignright” width=“300”] Pretty structs, as far as the eye can see…[/caption] struct
s 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
}
### 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](https://facility9.com/2016/03/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: https://gist.github.com/peschkaj/51a6da274b089f4c978791dcaf58a953 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 struct
s in Rust, check out the Rust book’s sections on Structs, Method Syntax, and the language reference about implementations.