Learning Rust: Structuring Data with Structs

Andrew Bone - Mar 18 - - Dev Community

Another week, another dive into Rust. This time, we're delving into structs. Structs bear resemblance to interfaces in TypeScript, enabling the grouping of intricate data sets within an object, much like TypeScript/JavaScript. Rust also accommodates functions within these structs, offering a semblance of classes, albeit with distinctions. Let's delve into this topic.

Ferris the crab

Instantiating Structs

Structs are not too dissimilar from tuples, which we discussed a couple of posts ago, in that they hold several different values that can have different types and can be destructured before use. To define a struct, we start with the keyword struct, followed by a name for our struct (starting with a capital letter), and describe the data between a set of curly braces.

struct Book {
  title: String,
  author: String,
  rating: f32,
  unix_release_date: i64,
}
Enter fullscreen mode Exit fullscreen mode

Now that we've defined our struct, we can use it. To use the struct, we simply assign it to a variable and fill out the key: value pairs (though the order in which we do this is not important). By default, the created variable is immutable but, as usual, adding the mut keyword before the variable name will make it mutable, allowing you to set keys like so: variable_name.key_name = "some str".

let book1 = Book {
  title: String::from("Learning Patterns: Patterns for Building Powerful Web Apps with Vanilla JavaScript and React"),
  author: String::from("Lydia Hallie · Addy Osmani"),
  rating: 4.8,
  unix_release_date: 1633042800,
};
Enter fullscreen mode Exit fullscreen mode

Struct instance ownership

Ownership can be transferred between struct instances by either updating a field from one instance to match that of another instance or by utilising the .. syntax to move selected parts of the instance over.

let mut book2 = Book {
  title: String::from("This is a placeholder book name."),
  author: String::from("This is a placeholder author."),
  ..book1
};

// At this point, book1.rating and book1.unix_release_date 
// have been moved to book2 and are no longer accessible

book2.author = book1.author;

// Now book1.author has been moved to book2 as well
// but book1.title is still accessible on book1
Enter fullscreen mode Exit fullscreen mode

Tuple structs

Tuple structs offer an alternative way to define a struct without naming the keys explicitly. Despite their similarity to tuples, tuple structs provide predefined structures, making it easier for developers to understand the data organisation. One significant advantage is the ability to enforce type safety, ensuring that tuples with the same internal types cannot be used interchangeably in functions where they are not intended.

struct RGB(f64, f64, f64);
struct Vec3(f64, f64, f64);

fn main() {
    let tomato = RGB(1.0, 0.38824, 0.27843);
    let start_position = Vec3(1.74531, 6.221, 3.132234);

    print_color(&tomato);
    print_vector_three(&start_position);

    // The following line would cause an error as it's the wrong type
    // print_color(&start_position);
}

// Print the RGB values out
fn print_color(color: &RGB) {
    println!("R {}, G {}, B {}", color.0, color.1, color.2);
}

// Print the vector3's XYZ values out
fn print_vector_three(vector3: &Vec3) {
    println!("X {}, Y {}, Z {}", vector3.0, vector3.1, vector3.2);
}
Enter fullscreen mode Exit fullscreen mode

Struct Methods

As mentioned earlier, methods make structs resemble JavaScript classes more closely than JavaScript objects. Structs may have methods, which are essentially functions added to them and can be called on an instance of a struct. We create a set of methods using the impl keyword, which then wraps the functions. Each method must have &self as its first parameter, which is a reference to the instance of the struct and all its data.

If we return to our book example from earlier we can add a method that will tell us if the book has more than 4 stars or not.

impl Book {
  // Method to check if the book is recommended based on its rating
  fn is_recommended(&self) -> bool {
    // Check if the rating is greater than 4 stars
    self.rating > 4.0
  }
}

// We can call this with book1.is_recommended();
Enter fullscreen mode Exit fullscreen mode

And if we wanted to add another parameter to our method function, we can simply add more parameter after &self. These need types and names as usual. Let's have a look at our final book example.

struct Book {
  title: String,
  author: String,
  rating: f32,
  unix_release_date: i64,
}

impl Book {
  // Method to check if the book is recommended based on its rating
  fn is_recommended(&self, star_limit: f32) -> bool {
    // Check if the rating is greater than the star_limit
    self.rating > star_limit
  }
}

const RECOMMEND_THRESHOLD: f32 = 3.5;

fn main() {
    let book1 = Book {
      title: String::from("Learning Patterns: Patterns for Building Powerful Web Apps with Vanilla JavaScript and React"),
      author: String::from("Lydia Hallie · Addy Osmani"),
      rating: 4.8,
      unix_release_date: 1633042800,
    };

    // Call the is_recommended method on book1
    let is_recommended = book1.is_recommended(RECOMMEND_THRESHOLD);

    if is_recommended {
        println!("This book is recommended!");
    } else {
        println!("This book is not recommended.");
    }
}
Enter fullscreen mode Exit fullscreen mode

End of week four

Thank you for joining me on this exploration. As we wrap up, remember, our Rust journey is far from over. Stay tuned for more insights as we delve deeper into its intricacies. Your feedback is invaluable, so keep it coming. And if you're on your own learning journey, share your series in the comments below. Let's keep learning together!

Thanks so much for reading. If you'd like to connect with me outside of Dev here are my twitter and linkedin come say hi 😊.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .