Добавление полезной функциональности с помощью производных трейтов
Было бы полезно иметь возможность выводить экземпляр 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
.