3.1.1 语义混淆

01-语义混淆 — Comprehensive Rust

译文 · 基于 Comprehensive Rust

原文链接: https://google.github.io/comprehensive-rust/idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.html

3.1.1 语义混淆

当函数接受多个同类型参数时,调用处含义不清:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
# struct LoginError;
fn login(username: &str, password: &str) -> Result<(), LoginError> {
    // [...]
    # Ok(())
}

fn main() {
    let password = "password";
    let username = "username";

    // 在代码库的另一处,我们误把参数对调了。
    // 最好情况是 bug,最坏情况是安全漏洞
    login(password, username);
}

Newtype 模式可以在编译期防止这类错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
struct Username(String);
struct Password(String);
struct LoginError;

fn login(username: &Username, password: &Password) -> Result<(), LoginError> {
    // [...]
    # Ok(())
}

fn main() {
    let password = Password("password".into());
    let username = Username("username".into());
    login(password, username); // 🛠️❌
}
  • 运行两个示例:展示原示例能成功编译,以及修改后示例返回的编译错误。

  • 强调语义角度。应利用 newtype 模式为不同概念使用不同的类型,从而彻底排除这类错误。

  • 不过也要注意:确实存在函数需要接受多个同类型参数的合理场景。若正确性至关重要,可考虑用带命名字段的结构体作为输入:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    #
    pub struct LoginArguments<'a> {
        pub username: &'a str,
        pub password: &'a str,
    }
    # fn login(i: LoginArguments) {}
    # let password = "password";
    # let username = "username";
    
    // 无需查看 `login` 函数定义就能发现问题。
    login(LoginArguments {
        username: password,
        password: username,
    })
    

    用户在调用处必须为每个字段赋值,从而更容易发现 bug。

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