Добавление полезной функциональности с помощью производных трейтов
Было бы полезно иметь возможность выводить экземпляр Rectangle при отладке программы и видеть значения всех его полей. Listing 5-11 пытается использовать макрос println!, как мы это делали в предыдущих главах. Однако, это не сработает.
Имя файла: src/main.rs
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {}", rect1);
}
Listing 5-11: Попытка вывести экземпляр Rectangle
Когда мы компилируем этот код, мы получаем ошибку с таким основным сообщением:
error[E0277]: `Rectangle` не реализует `std::fmt::Display`
Макрос println! может выполнять много видов форматирования, и по умолчанию фигурные скобки сообщают println! использовать форматирование, называемое Display: вывод, предназначенный для непосредственного потребления конечным пользователем. Примитивные типы, которые мы видели до сих пор, по умолчанию реализуют Display, потому что существует только один способ, которым вы хотите показать 1 или любой другой примитивный тип пользователю. Но при работе со структурами менее ясно, как println! должен форматировать вывод, потому что есть больше возможностей отображения: нужны ли запятые? Хотите ли вы печатать фигурные скобки? Следует ли показывать все поля? В связи с этой неоднозначностью Rust не пытается угадать, что мы хотим, и структурам не предоставляется реализация Display, которую можно использовать с println! и占位符ом {}.
Если мы продолжим читать ошибки, мы увидим эту полезную заметку:
= help: трейт `std::fmt::Display` не реализован для `Rectangle`
= note: в строках форматирования вы можете использовать `{:?}` (или {:#?} для
красивого вывода) вместо этого
Попробуем! Теперь вызов макроса println! будет выглядеть так: println!("rect1 is {:?}", rect1);. Размещение спецификатора :? внутри фигурных скобок сообщает println!, что мы хотим использовать формат вывода, называемый Debug. Трейт Debug позволяет нам выводить нашу структуру таким образом, который полезен для разработчиков, чтобы мы могли увидеть ее значение при отладке кода.
Компилируем код с этой правкой. Упс! Мы по-прежнему получаем ошибку:
error[E0277]: `Rectangle` не реализует `Debug`
Но снова компилятор дает нам полезную заметку:
= help: трейт `Debug` не реализован для `Rectangle`
= note: добавьте `#[derive(Debug)]` или реализуйте `Debug` вручную
Rust есть функциональность для вывода отладочной информации, но мы должны явно включить ее, чтобы она стала доступной для нашей структуры. Для этого мы добавляем внешний атрибут #[derive(Debug)] сразу перед определением структуры, как показано в Listing 5-12.
Имя файла: src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
Listing 5-12: Добавление атрибута для вывода трейта Debug и вывод экземпляра Rectangle с использованием отладочного форматирования
Теперь, когда мы запускаем программу, мы не получим никаких ошибок, и мы увидим следующий вывод:
rect1 is Rectangle { width: 30, height: 50 }
Отлично! Это не самый красивый вывод, но он показывает значения всех полей для этого экземпляра, что определенно поможет при отладке. Когда у нас есть большие структуры, полезно иметь вывод, который легче читать; в таких случаях мы можем использовать {:#?} вместо {:?} в строке println!. В этом примере использование стиля {:#?} выведет следующее:
rect1 is Rectangle {
width: 30,
height: 50,
}
Другой способ вывести значение с использованием формата Debug - это использовать макрос dbg!, который получает владение выражением (в отличие от println!, который получает ссылку), выводит имя файла и номер строки, где происходит вызов макроса dbg! в вашем коде, вместе с результирующим значением этого выражения и возвращает владение значением.
Примечание: вызов макроса dbg! выводит в стандартный поток ошибок консоли (stderr), в отличие от println!, который выводит в стандартный поток вывода консоли (stdout). Мы поговорим больше о stderr и stdout в разделе "Запись сообщений об ошибках в стандартный поток ошибок вместо стандартного потока вывода".
Вот пример, в котором мы интересуемся значением, которое присваивается полю width, а также значением всей структуры в rect1:
Имя файла: src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
1 width: dbg!(30 * scale),
height: 50,
};
2 dbg!(&rect1);
}
Мы можем поместить dbg! вокруг выражения 30 * scale [1], и, так как dbg! возвращает владение значением выражения, поле width получит то же значение, что и если бы мы не использовали вызов dbg! там. Мы не хотим, чтобы dbg! получил владение rect1, поэтому в следующем вызове мы используем ссылку на rect1 [2]. Вот, как выглядит вывод этого примера:
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect1 = Rectangle {
width: 60,
height: 50,
}
Мы видим, что первый кусок вывода появился из [1], где мы отлаживаем выражение 30 * scale, и его результирующее значение равно 60 (реализация форматирования Debug для целых чисел - это вывод только их значения). Вызов dbg! в [2] выводит значение &rect1, которое представляет собой структуру Rectangle. Этот вывод использует красивый формат Debug для типа Rectangle. Макрос dbg! может быть действительно полезен, когда вы пытаетесь понять, что делает ваш код!
Кроме трейта Debug, Rust предоставил ряд трейтов, которые мы можем использовать с атрибутом derive, которые могут добавить полезное поведение к нашим пользовательским типам. Эти трейты и их поведение перечислены в Приложении C. Мы рассмотрим, как реализовать эти трейты с пользовательским поведением, а также как создавать свои собственные трейты в главе 10. Есть также многие атрибуты, кроме derive; для получения дополнительной информации см. раздел "Атрибуты" в Справочнике по Rust по адресу https://doc.rust-lang.org/reference/attributes.html.
Наша функция area очень специфична: она только вычисляет площадь прямоугольников. Будет полезно связать это поведение более тесно с нашей структурой Rectangle, потому что оно не будет работать с любым другим типом. Посмотрим, как мы можем продолжить рефакторинг этого кода, превращая функцию area в метод area, определенный для нашего типа Rectangle.