Приватные поля
Часть раздела Object Oriented Programming путешествия по Rust на Coddy — урок 10 из 61.
В предыдущем уроке вы сделали всё публичным — struct, её поля и методы. Но истинная инкапсуляция означает контроль над тем, как можно получить доступ к вашим данным. В Rust вы можете сделать struct публичной, оставив её поля приватными.
Рассмотрим Wallet, который никогда не должен иметь отрицательный баланс. Если поле является публичным, любой сможет установить для него любое значение:
// wallet.rs
pub struct Wallet {
balance: f64, // Нет pub = приватное!
}
impl Wallet {
pub fn new(initial: f64) -> Wallet {
Wallet { balance: initial }
}
}
Обратите внимание, что Wallet является публичным, но balance не имеет ключевого слова pub. Это означает, что код вне модуля может создать Wallet, используя конструктор, но не может напрямую читать или изменять поле balance:
// main.rs
mod wallet;
use wallet::Wallet;
fn main() {
let w = Wallet::new(100.0);
// println!("{}", w.balance); // Ошибка! поле является приватным
// w.balance = -50.0; // Ошибка! невозможно получить доступ к приватному полю
}
Это основа инкапсуляции — структура управляет своими собственными данными. Внешний код должен обращаться через методы, которые вы предоставляете, что означает, что вы можете добавить валидацию, логирование или любую другую логику. Данные остаются защищенными от недопустимых состояний.
В следующих уроках вы узнаете, как обеспечить контролируемый доступ к этим приватным данным с помощью методов геттеров и сеттеров.
Задание
ЛегкоДавайте создадим систему температурных датчиков, которая демонстрирует инкапсуляцию, сохраняя конфиденциальные данные защищенными внутри модуля.
Вы создадите два файла для организации своего кода:
sensor.rs: Определите структуруTemperatureSensorс двумя полями:location(String) иreading(f64). Структура должна быть публичной, чтобы ее можно было использовать из других файлов, но оставьте оба поля приватными — внешний код не должен иметь возможности напрямую обращаться к данным датчика или изменять их. Добавьте публичный конструкторnew, который принимает название местоположения и начальное показание температуры.main.rs: Подключите модуль датчика и создайтеTemperatureSensor, используя конструктор. Поскольку поля приватные, вы не сможете вывести их напрямую — вместо этого выведите подтверждающее сообщение, показывающее, что датчик был успешно создан.
Эта задача сосредоточена на основной концепции приватных полей: структура доступна, но ее внутренние данные защищены. Внешний код может взаимодействовать с датчиком только через методы, которые вы предоставляете (в данном случае только через конструктор).
Ваш вывод должен соответствовать следующему формату:
Sensor created for: {location}Чтобы получить доступ к местоположению для вывода на печать, вам нужно, чтобы ваша функция new выводила это сообщение перед возвратом датчика, так как само поле является приватным.
Например, если вы создадите датчик с местоположением "Kitchen" и показанием 22.5, вывод будет следующим:
Sensor created for: KitchenВы получите два входных значения: название местоположения и начальное показание температуры.
Шпаргалка
В Rust вы можете сделать структуру публичной, сохраняя её поля приватными для достижения инкапсуляции. Это предотвращает прямой доступ или изменение внутренних данных внешним кодом.
Чтобы сделать поле приватным, просто опустите ключевое слово pub:
pub struct Wallet {
balance: f64, // Нет pub = приватное поле
}
impl Wallet {
pub fn new(initial: f64) -> Wallet {
Wallet { balance: initial }
}
}
В этом примере Wallet является публичной (может использоваться вне модуля), но balance является приватным (не может быть доступно напрямую извне).
Внешний код может создавать экземпляры, используя публичные конструкторы, но не может читать или изменять приватные поля:
mod wallet;
use wallet::Wallet;
fn main() {
let w = Wallet::new(100.0);
// println!("{}", w.balance); // Ошибка! поле приватное
// w.balance = -50.0; // Ошибка! невозможно получить доступ к приватному полю
}
Такая инкапсуляция гарантирует, что структура сама управляет своими данными, а внешний код должен использовать предоставленные вами методы для взаимодействия с ней.
Попробуйте сами
mod sensor;
use sensor::TemperatureSensor;
fn main() {
// Чтение входных данных
let mut location = String::new();
std::io::stdin().read_line(&mut location).expect("Failed to read line");
let location = location.trim().to_string();
let mut reading_str = String::new();
std::io::stdin().read_line(&mut reading_str).expect("Failed to read line");
let reading: f64 = reading_str.trim().parse().expect("Failed to parse reading");
// TODO: Создайте TemperatureSensor, используя конструктор
// Функция new сама обработает вывод подтверждающего сообщения
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media Player