Clippy suggests use #[non_exhaustive] instead of using private field

My goal is to prevent creation of a struct outside its module. Clippy says, "this seems like a manual implementation of the non-exhaustive pattern". It goes on to suggest the use of #[non_exhaustive] and the removal of the private field.

mod event {
    pub struct Event {
        pub name: String,
        pub start: usize,
        pub end: usize,
        // Prevents direct creation of Event outside of module, Event::new() must be used
        _private: (),
    }
    // ...

    impl Event {
        pub fn new(name: &str, start: usize, end: usize) -> Self { ... }
    }
}

To silence Clippy I can add #![allow(clippy::manual_non_exhaustive)].

If I accept Clippy's suggestion, use #[non_exhaustive] and remove the private field, I can still create an Event outside the module within my project without going through Event::new():

let test_event = Event {
    name: "World War I".to_string(),
    start: 1914,
    end: 1918,
};

What is the correct/idiomatic/most pragmatic way to do what I want?

The #[non_exhaustive] attribute will make calling the constructor impossible only outside of the current crate. For privacy-barriers within a crate, it won’t help, so then, adding a line like

#[allow(clippy::manual_non_exhaustive)] // we want intra-crate privacy

on your struct definition would be idiomatic IMO; in general silencing clippy hints is idiomatic, if you have a good reason not to follow the lint’s suggestion, as would be the case here. If your crate uses this kind of construct a lot, the lint could be more annoying than useful in general, and you could as well use the #![allow(…)] once on a crate-level. Or if you simply disagree with the lint being generally useful at all, feel free to disable it crate-wide based on such a reasoning. Fine-tuning the set of clippy lints you want or don’t want is very normal, in my opinion. Disagreeing with individual lints and turning them off is fine… heck, quite a few people disagree with so many lints that they won’t use clippy at all, so you’re all good :slight_smile:

9 Likes

What steffahn said is all completely correct, but I'll poke at this a bit.

Why do you need new to be used for this? What harm comes from something else in the crate constructing it? non_exhaustive is crate-based because of semver, but that's not a problem for anything inside the crate, because you could update it at the same time as publishing the new version.

And it can't be to maintain invariants, because all the fields are public so anything can muck with them.

4 Likes

What I really want is a struct that cannot be mutated.

The Event::new IRL does all the field validations and returns a Result, containing either the Event or an Error describing the problem/s.

You are right, of course: anyone can change the fields after construction - I missed that ...

I would have liked the possibility to read the fields directly, e.g. event.name. Do I have no other choice than making all fields private and provide getter-functions for the fields?

1 Like

That is correct.

As of November, there is an accepted RFC for restrictions on mutability, but it is not yet implemented.

https://rust-lang.github.io/rfcs/3323-restrictions.html

4 Likes

It's worth noting that once a bug is resolved with the PR I have open, it can be merged. So it should land on nightly relatively soon.

1 Like