I have a basic type with some functionality, including trait implementations:
use std::fmt;
use std::str::FromStr;
pub struct MyIdentifier {
value: String,
There are several ways to deal with this kind of problem. The following solution is using the so-called newtype pattern, a unified trait for the object the newtype contains and a trait implementation for the newtype.
(Explanation is going to be inline, but if you'd like to see the code as a whole and at the same time test it then go to the playground.)
First, we create a trait that describes the minimal behaviour we'd like to see from an identifier. In Rust you don't have inheritance, you have composition, i.e. an object can implement any number of traits which will describe its behaviour. If you'd like to have something that is common in all your objects — which you would achieve via inheritance — then you have to implement the same trait for them.
use std::fmt;
trait Identifier {
fn value(&self) -> &str;
}
Then we create a newtype which contains a single value which is a generic type that is constrained to implement our Identifier trait. The great thing about this pattern is that it will actually be optimised by the compiler at the end.
struct Id(T);
Now that we have a concrete type, we implement the Display trait for it. Since Id's internal object is an Identifier, we can call the value method on it so we only have to implement this trait once.
impl fmt::Display for Id
where
T: Identifier,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.value())
}
}
The followings are definitions of different identifier types and their Identifier trait implementations:
struct MyIdentifier(String);
impl Identifier for MyIdentifier {
fn value(&self) -> &str {
self.0.as_str()
}
}
struct MyUserIdentifier {
value: String,
user: String,
}
impl Identifier for MyUserIdentifier {
fn value(&self) -> &str {
self.value.as_str()
}
}
And last but not least, this is how you would use them:
fn main() {
let mid = Id(MyIdentifier("Hello".to_string()));
let uid = Id(MyUserIdentifier {
value: "World".to_string(),
user: "Cybran".to_string(),
});
println!("{}", mid);
println!("{}", uid);
}
The Display was easy, however I don't think you could unify the FromStr, as my example above demonstrates it is very likely that the different identifiers have different fields not just the value (to be fair, some don't even have the value, after all, the Identifier trait only requires the object to implement a method called value). And semantically the FromStr supposed to construct a new instance from a string. Therefore I would implement FromStr for all types separately.