Non-exhaustive structs will future-proof our codes by preventing client codes from using Struct expressions. We use Struct expressions to instantiate structs, and they’re JSON-like expression in appearance with all fields present. When we update a struct with a new field, all expressions must be updated to include that field.
The non_exhaustive attribute indicates that a type or variant may have more fields or variants added in the future. It can be applied to structs, enums, and enum variants.
For example, we have a struct Pet with two initial fields name and age.
1 2 3 4 | struct Pet { name: String, age: i32 } |
We can instantiate it using a struct expression as follows.
1 2 3 4 | let my_pet = Pet { name: "Jake".to_string(), age: 2 }; |
The codes work until we add a new field owner to the struct.
Wouldn’t it be better if we could just disallow struct expressions? We could use #[non_exhaustive]!
Crate-Level-Only Structs
There is one important thing to note about #[non_exhaustive]; it only works at the crate level.
![Using #[non_exhaustive] for Non-exhaustive Structs Using #[non_exhaustive] for Non-exhaustive Structs](https://turreta.com/wp-content/uploads/2019/12/2020-01-01_9-44-37.png)
Using #[non_exhaustive] for Non-exhaustive Structs
Crate mycrate lib.rs
This crate has the non-exhaustive structs. These are just structs marked with #[non_exhaustive]. The following codes have a struct with two public factory functions to instantiate it in the client codes. They are using #[non_exhaustive] on structs, which in this case, is just Person.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #[non_exhaustive] #[derive(Debug)] pub struct Person { pub first_name: String, pub last_name: String } impl Person { pub fn new() -> Person { Person { first_name: "John".to_string(), last_name: "Doe".to_string() } } pub fn new_with_fname_lname(fname: String, lname: String) -> Person { Person { first_name: fname, last_name: lname } } } |
Crate myapp
This crate uses mycrate that has those non-exhaustive structs. Any attempts to use struct expressions to instantiate those structs will fail the building process.
Cargo.toml
1 2 3 4 5 6 7 8 9 | [package] name = "rust1_40features" version = "0.1.0" authors = ["Karl San Gabriel"] edition = "2018" [dependencies] # Change path accordingly mycrate={version="0.1.0", path = "..\\rust\\mycrate" } |
main.rs
1 2 3 4 5 6 7 8 9 10 11 | extern crate mycrate; use mycrate::Person; fn main() { let p = Person::new(); println!("{:?}", p); let another_p = Person::new_with_fname_lname("Karl".to_string(), "San Gabriel".to_string()); println!("{:?}", another_p); } |
Output:
1 2 | Person { first_name: "John", last_name: "Doe" } Person { first_name: "Karl", last_name: "San Gabriel" } |
Tested with Rust 1.40.0.