McGrady
McGrady

Reputation: 11477

How to restrict the construction of struct?

Is it possible to forbid creating an instances directly from member initialization?

e.g.

pub struct Person {
    name: String,
    age: u8,
}

impl Person {
    pub fn new(age: u8, name: String) -> Person {
        if age < 18 {
            panic!("Can not create instance");
        }
        Person { age, name }
    }
}

I can still use

Person {
    age: 6, 
    name: String::from("mike")
}

to create instance. Is there anyway to avoid this?

Upvotes: 42

Views: 10717

Answers (2)

hellow
hellow

Reputation: 13420

Answer to question

You cannot create that struct from member initialization, because members are by default private and cannot be used directly. Only the immediate module and its submodules can access private fields, functions, ... (see the book about visibility).

Your example works, because your function is in that certain scope.

mod foo {
    pub struct Person {
        name: String,
        age: u8,
    }

    impl Person {
        pub fn new(age: u8, name: String) -> Person {
            if age < 18 {
                panic!("Can not create instance");
            }
            Person { age, name }
        }
    }
}

use foo::Person; // imagine foo is an external crate

fn main() {
    let p = Person {
        name: String::from("Peter"),
        age: 8,
    };
}

(Playground)

error[E0451]: field `name` of struct `Person` is private
error[E0451]: field `age` of struct `Person` is private

Make it possible to create a struct from the outside

On the other hand, if you want to make it possible to create an instance by member initialization, use the pub keyword in front of all members.

pub struct Person {
    pub name: String,
    pub age: u8,
}

Please note, that you can "bypass" the constructs requirement of age >= 18 with this, so use it wisely. If this is not desired, use a get method for the fields.


Make it possible to access the fields, but not creating a struct from the outside

Please see KittenOverflows answer for a different approach to this.

--

Sometimes it's useful to let the user of your crate access the members directly, but you want to restrict the creation of an instance to your "constructors". Just add a private field.

pub struct Person {
    pub name: String,
    pub age: u8,
    _private: ()
}

Because you cannot access _private, you cannot create an instance of Person directly.

Also the _private field prevents creating a struct via the update syntax, so this fails:

/// same code from above

fn main() {
    let p = Person::new(8, String::from("Peter"));
    let p2 = Person { age: 10, ..p };
}
error[E0451]: field `_private` of struct `foo::Person` is private
  --> src/main.rs:27:34
   |
27 |     let p2 = Person { age: 10, ..p };
   |                                  ^ field `_private` is private

Upvotes: 67

user697748
user697748

Reputation:

For Rust >= 1.40.0, consider applying the non_exhaustive attribute to your struct.

// Callers from outside my crate can't directly construct me
// or exhaustively match on my fields!
#[non_exhaustive]
pub struct Settings {
  pub smarf: i32,
  pub narf: i32,
}

More info in the 1.40.0 release notes.

Upvotes: 22

Related Questions