3.1.2 强制不变量

02-强制不变量 — Comprehensive Rust

译文 · 基于 Comprehensive Rust

原文链接: https://google.github.io/comprehensive-rust/idiomatic/leveraging-the-type-system/newtype-pattern/parse-don-t-validate.html

3.1.2 强制不变量

可以利用 newtype 模式强制不变量(invariants)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
pub struct Username(String);

impl Username {
    pub fn new(username: String) -> Result<Self, InvalidUsername> {
        if username.is_empty() {
            return Err(InvalidUsername::CannotBeEmpty)
        }
        if username.len() > 32 {
            return Err(InvalidUsername::TooLong { len: username.len() })
        }
        Ok(Self(username))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}
# pub enum InvalidUsername {
#     CannotBeEmpty,
#     TooLong { len: usize },
# }
  • Newtype 模式结合 Rust 的模块与可见性系统,可以保证某类型的实例满足一组不变量。

    在上例中,Username 结构体内的原始 String 无法从其他模块或 crate 直接访问,因为它未标为 pub 或 pub(in ...)。Username 的使用者必须通过 new 方法创建实例。而 new 会执行校验,从而确保所有 Username 实例都满足这些检查。

  • as_str 方法允许使用者访问原始字符串表示(例如写入数据库)。但使用者不能修改底层值,因为返回类型 &str 限制为只读访问。

  • 类型级不变量还有二阶收益。

    输入在边界处校验一次,程序其余部分可以依赖这些不变量始终成立。我们可以避免在程序各处做冗余校验和「防御性编程」检查,从而减少噪音并提升性能。

最后修改 August 11, 2026: 更新 (70a5af133)