Перейти к основному содержимому

IDE — уведомления на Rust

Пример показывает полный сценарий: условие для студента, запускаемую заготовку, эталон автора, проверку вывода и обратную связь. Порядок создания заданий описан в настройке IDE.

Условие для студента​

Разработайте систему уведомлений с использованием трейтов и трейт-объектов:

  1. В трейте Notifier объявите метод notify(&self, message: &str).
  2. Реализуйте трейт для структур EmailNotifier и SMSNotifier.
  3. В send_notifications примите список &[Box<dyn Notifier>] и вызовите notify у каждого элемента.
  4. Отправьте сообщение «Ваш заказ отправлен!» обоими способами.

Ожидаемый вывод:

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(&notifiers, "Ваш заказ отправлен!");
}

Попросите студента заполнить места с 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(&notifiers, "Ваш заказ отправлен!");
}

В заготовке включите Проверять вывод по эталону и вставьте две строки ожидаемого вывода. Для оценки структуры кода и рекомендаций включите Использовать ИИ для проверки решения.

Объяснение ошибки компиляции​

Если студент удалит объявление 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(&notifiers, "Test");
assert_eq!(*messages.borrow(), vec!["Test".to_owned(), "Test".to_owned()]);
}
}

Здесь проверка использует общее хранилище сообщений: дополнительный метод as_any и приведение трейт-объекта к конкретному типу не нужны.