Семантическая путаница
Когда функция принимает несколько аргументов одного типа, места вызова становятся неясными:
// 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, }) }На месте вызова пользователь вынужден присвоить значения каждому полю, что увеличивает вероятность заметить ошибки.