Search code examples
rustdefaultprivatepublic

Rust impl default trait with private fields


I'm getting an error when I have this sort of setup:

default_test.rs:

mod default_mod;

use default_mod::Point;

fn main() {
    let _p1 = Point::new();
    let _p2: Point = Point {
        z: 1,
        ..Default::default()
    };
}

default_mod.rs:

pub struct Point {
    x: i32,
    y: i32,
    pub z: i32,
}

impl Point {
    pub fn new() -> Self {
        Point { x: 0, y: 0, z: 0 }
    }
}

impl Default for Point {
    fn default() -> Self {
        Point { x: 0, y: 0, z: 0 }
    }
}

which gives the compiler error:

default_test.rs:9:7
  |
9 |     ..Default::default()
  |       ^^^^^^^^^^^^^^^^^^ field `x` is private

error[E0451]: field `y` of struct `default_mod::Point` is private

Short version - I have a struct with both public and private fields. I would like to initialise this struct with default values, but sometimes override them.

I can't seem to fix this error, nor seen anything on the internet or the docs that even mentions errors like this.

It surprises me, because I'd think a common use-case would be to initialise a struct, and that some members of the struct would be private so you can hide implementation details behind and interface.

In my case the private field is a Vec as I have some logic that needs to go into adding or removing things from the vector, so I want to make it private to prevent anyone messing up the data structure.

What are my options here?


Solution

  • It surprises me, because I'd think a common use-case would be to initialise a struct, and that some members of the struct would be private so you can hide implementation details behind and interface.

    The problem is that the struct update syntax doesn't do what you think it does. For example, the book shows the following code:

    let user2 = User {
        email: String::from("[email protected]"),
        username: String::from("anotherusername567"),
        ..user1
    };
    

    The ..user1 syntax fills in the User fields we haven't explicitly specified, such as active: user1.active, signin_count: user1.signin_count. .. may be followed by an arbitrary expression which returns the structure, which is where Default::default() comes into play, and means the same as User::default() because a User is expected. However, the desugaring remains unchanged and boils down to assigning individual fields, in neither case granting special access to private fields.

    To get back to your example, this code:

    let p = Point {
        z: 1,
        ..Default::default()
    };
    

    is syntactic sugar for:

    let p = {
        let _tmp = Point::default();
        Point {
            x: _tmp.x, // assigning private field
            y: _tmp.y, // assigning private field
            z: 1,
        }
    };
    

    and not for the expected:

    // NOT what happens
    let p = {
        let _tmp = Point::default();
        p.z = 1; // no private assignment
        _tmp
    };
    

    What are my options here?

    The most idiomatic option is to provide a builder for Point. That is also somewhat bulky1, so if you're looking for a simple solution, you could also use Point::default() and set the z attribute manually. The struct update syntax is incompatible with structs with private fields and just not useful for your type.


    1 Though there are crates like derive_builder, typed-builder and builder-pattern that take some of the drudgery away.