14 February 2023
A trait is an interface that types can implement. It consists of a set of associated items, which can be types, constants, functions, and methods. As an example, let’s take a look at a trait used for logging messages with a log level:
trait Logger {
// associated type
type Level;
// associated constant
const DEFAULT_LEVEL: Self::Level;
// associated function
fn new() -> Self;
// method
fn log(&self, msg: &str, level: Self::Level);
// method with default implementation
fn log_error(&self, msg: &str) {
self.log(msg, Self::DEFAULT_LEVEL);
}
// mutating method
fn set_minimum_level(&mut self, level: Self::Level);
}
The items in a trait can be either declarations, meaning constants without a value or functions without a function body, or definitions. A definition provides a default value or implementation that an implementation of the trait may or may not override. Types are always declarations, though.
In a trait definition, Self
stands for the implementing type.
Let’s implement the trait. We’ll need two types here, one that we’ll assign to Level
and the one that will actually implement Logger
:
#[derive(Debug, PartialEq, PartialOrd)]
enum SimpleLevel {
Debug,
Info,
Error,
}
struct SimpleLogger {
minimum_level: SimpleLevel,
}
To implement the trait we need a trait implementation that includes a definition for each item in the trait (the ones with a default implementation are optional):
impl Logger for SimpleLogger {
type Level = SimpleLevel;
const DEFAULT_LEVEL: SimpleLevel = SimpleLevel::Debug;
fn new() -> SimpleLogger {
Self {
minimum_level: Self::DEFAULT_LEVEL,
}
}
fn log(&self, msg: &str, level: SimpleLevel) {
if level < self.minimum_level {
return;
}
println!("[{:?}] {}", level, msg);
}
fn set_minimum_level(&mut self, level: SimpleLevel) {
self.minimum_level = level;
}
}
This looks pretty similar to the inherent implementations in the previous article, Rust: Methods, except we specify the trait name at the top.
Here’s some code using the function and methods from the trait:
let mut logger = SimpleLogger::new();
logger.log("Hello!", SimpleLevel::Info); // prints: [Info] Hello!
logger.set_minimum_level(SimpleLevel::Error);
logger.log("Hello!", SimpleLevel::Info); // does not print message