Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Семантическая путаница

Когда функция принимает несколько аргументов одного типа, места вызова становятся неясными:

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

    // В другой части кодовой базы мы по ошибке меняем аргументы местами.
    // Ошибка (в лучшем случае), уязвимость безопасности (в худшем)
    login(password, username);
}

Паттерн newtype может предотвратить этот класс ошибок во время компиляции:

// 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 следует использовать, чтобы для разных понятий были разные типы, что полностью исключает этот класс ошибок.

  • Тем не менее заметьте, что есть законные сценарии, когда функция может принимать несколько аргументов одного типа. В таких случаях, если правильность имеет первостепенное значение, рассмотрите структуру с именованными полями в качестве входных данных:

    #![allow(unused)]
    fn main() {
    // 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,
    })
    }

    На месте вызова пользователь вынужден присвоить значения каждому полю, что увеличивает вероятность заметить ошибки.