如何限制结构体的构建?

42

是否有可能禁止直接从成员初始化创建实例?

例如:

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 }
    }
}

我仍然可以使用

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

如何避免创建实例?

2个回答

62

问题的答案

您无法通过成员初始化来创建该结构体,因为成员默认为私有且不能直接使用。只有直接模块及其子模块才能访问私有字段、函数等(请参阅关于可见性的书籍)。

您的示例有效,因为您的函数在该特定作用域中。

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,
    };
}

(游乐场)

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

使外部能够创建结构体

另一方面,如果您想通过成员初始化创建实例,则在所有成员前面使用pub关键字。

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

请注意,您可以用这种方式“跳过”age >= 18的要求,因此请明智使用。如果不希望这样做,请使用字段的get方法。

允许访问字段,但不要从外部创建结构体

请参见 KittenOverflows答案,了解另一种方法。

--

有时候让你的板条箱用户直接访问成员很有用,但你希望限制实例的创建只能通过“构造函数”进行。只需添加一个私有字段即可。
pub struct Person {
    pub name: String,
    pub age: u8,
    _private: ()
}

由于您无法访问 _private,因此无法直接创建 Person 实例。

_private 字段还防止通过update 语法创建结构体,因此会失败:

/// 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

你提出的私有成员建议正是大多数读者想要的。你引用的另一个答案在任何方面都似乎不更好。 - Jeff Schwab
令人讨厌的是,Clippy建议私有成员方法是一种反模式,更倾向于使用#[non_exhaustive] - undefined
是的,我对此有些可以接受。为此,请使用non_exhaustive属性,它更适合这种情况。 唯一的区别在于更新语法,如果您需要,请在结构体上使用#[allow(clippy::manual_non_exhaustive)] - undefined

21

对于 Rust >= 1.40.0,考虑将 non_exhaustive 属性应用于您的结构体。

// 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,
}

更多信息在1.40.0版发行说明中。


4
尽管我认为属性会更加简洁,但这感觉像是利用一个特性的副作用而不是其主要用途,这可能会向您的API用户发送错误的信息。 - Matias Pequeno
这可能适用于跨板条框架,但它并没有帮助更常见的情况,即将构造保留为单个模块的私有(而不是整个板条箱)。正如@MatiasPequeno指出的那样,这也是属性的一种不透明滥用。 - Jeff Schwab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接