IDE — уведомления на Rust
Пример показывает полный сценарий: условие для студента, запускаемую заготовку, эталон автора, проверку вывода и обратную связь. Порядок создания заданий описан в настройке IDE.
Условие для студента
Разработайте систему уведомлений с использованием трейтов и трейт-объектов:
- В трейте
Notifierобъявите методnotify(&self, message: &str). - Реализуйте трейт для структур
EmailNotifierиSMSNotifier. - В
send_notificationsпримите список&[Box<dyn Notifier>]и вызовитеnotifyу каждого элемента. - Отправьте сообщение «Ваш заказ отправлен!» обоими способами.
Ожидаемый вывод:
Email: Ваш заказ отправлен!
SMS: Ваш заказ отправлен!
Заготовка
Создайте IDE-задание Rust Compiler с файлом main.rs. Заготовка уже запускается, но ещё не выполняет задачу:
// Задание: реализуйте Notifier::notify для EmailNotifier и SMSNotifier.
// Метод печатает сообщение с префиксом "Email: " или "SMS: ".
// В send_notifications вызовите notify для каждого элемента списка.
trait Notifier {
fn notify(&self, message: &str);
}
struct EmailNotifier;
struct SMSNotifier;
impl Notifier for EmailNotifier {
fn notify(&self, _message: &str) {
// TODO: выведите сообщение с префиксом Email:
}
}
impl Notifier for SMSNotifier {
fn notify(&self, _message: &str) {
// TODO: выведите сообщение с префиксом SMS:
}
}
fn send_notifications(_notifiers: &[Box<dyn Notifier>], _message: &str) {
// TODO: вызовите notify для каждого элемента списка
}
fn main() {
let notifiers: Vec<Box<dyn Notifier>> = vec![
Box::new(EmailNotifier),
Box::new(SMSNotifier),
];
send_notifications(¬ifiers, "Ваш заказ отправлен!");
}
Попросите студента заполнить места с TODO и переименовать параметры _message и _notifiers, когда они начнут использоваться.
Эталон автора
Создайте отдельное задание того же типа, сохраните правильный код и выберите его в поле Эталонное решение заготовки.
trait Notifier {
fn notify(&self, message: &str);
}
struct EmailNotifier;
struct SMSNotifier;
impl Notifier for EmailNotifier {
fn notify(&self, message: &str) {
println!("Email: {}", message);
}
}
impl Notifier for SMSNotifier {
fn notify(&self, message: &str) {
println!("SMS: {}", message);
}
}
fn send_notifications(notifiers: &[Box<dyn Notifier>], message: &str) {
for notifier in notifiers {
notifier.notify(message);
}
}
fn main() {
let notifiers: Vec<Box<dyn Notifier>> = vec![
Box::new(EmailNotifier),
Box::new(SMSNotifier),
];
send_notifications(¬ifiers, "Ваш заказ отправлен!");
}
В заготовке включите Проверять вывод по эталону и вставьте две строки ожидаемого вывода. Для оценки структуры кода и рекомендаций включите Использовать ИИ для проверки решения.
Объяснение ошибки компиляции
Если студент удалит объявление EmailNotifier, но оставит его использование в main, компилятор сообщит, что имя не найдено. Кнопка Объяснить помогает разобрать причину.
Пример объяснения:
В списке уведомлений используется
EmailNotifier, но структура с таким именем не объявлена. Проверьте её объявление и реализациюNotifier. Для выполнения этого задания нужны оба способа уведомлений, поэтому удаление строки из списка не завершит задачу.
Предупреждения о неиспользуемых параметрах отличаются от ошибок компиляции: программа может запуститься, но не выполнить требования.
Обратная связь при незавершённом решении
Успешный запуск сам по себе не означает правильное решение. Например, пустые тела методов компилируются, но не отправляют уведомления.
Пример разбора:
- Структуры и трейт объявлены, точка входа подготовлена.
- В реализациях
notifyещё нет вывода сообщения. send_notificationsне вызывает метод у элементов списка.- Следующий шаг — реализовать один способ уведомления, проверить его вывод и затем добавить обход списка.
Если инструкция запрещает выдавать готовый ответ, обратная связь должна ограничиваться подсказками. Полный код из раздела «Эталон автора» предназначен для настройки проверки.
Обратная связь при верном решении
Пример разбора:
- Трейт задаёт общий интерфейс для разных способов уведомлений.
Box<dyn Notifier>позволяет хранить в одном списке разные реализации.- Отправка вынесена в отдельную функцию, а вывод соответствует условию.
- Для развития решения можно добавить третий канал, например Push, параметры получателя, логирование или тесты.
Rc имеет смысл при совместном владении объектом; для этого задания достаточно Box. Статический диспетчинг через обобщённые типы — отдельный вариант для обсуждения, но он меняет способ организации списка разнородных объектов.
Пример теста вызова метода
Тест проверяет, что send_notifications действительно вызывает notify и передаёт сообщение. Добавьте его в конец файла эталона. Для запуска тестов в PumpRoom используйте проект Cargo Test с соответствующей структурой Cargo, а не обычный запуск Rust Compiler.
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::rc::Rc;
struct TestNotifier {
messages: Rc<RefCell<Vec<String>>>,
}
impl Notifier for TestNotifier {
fn notify(&self, message: &str) {
self.messages.borrow_mut().push(message.to_owned());
}
}
#[test]
fn sends_message_to_every_notifier() {
let messages = Rc::new(RefCell::new(Vec::new()));
let notifiers: Vec<Box<dyn Notifier>> = vec![
Box::new(TestNotifier { messages: Rc::clone(&messages) }),
Box::new(TestNotifier { messages: Rc::clone(&messages) }),
];
send_notifications(¬ifiers, "Test");
assert_eq!(*messages.borrow(), vec!["Test".to_owned(), "Test".to_owned()]);
}
}
Здесь проверка использует общее хранилище сообщений: дополнительный метод as_any и приведение трейт-объекта к конкретному типу не нужны.