Существует несколько способов решения этой проблемы. Следующее решение использует так называемый шаблон newtype , унифицированную черту для объекта, который содержит newtype, и реализацию черты для нового типа.
(Объяснение будет встроенным, но если вы хотите увидеть код целиком и одновременно протестировать его, перейдите на площадку .)
Сначала мы создаем черту, которая описывает минимальное поведение, которое мы хотели бы видеть из идентификатора. В Rust у вас нет наследования, у вас есть композиция, то есть объект может реализовывать любое количество признаков, которые будут описывать его поведение. Если вы хотите, чтобы во всех ваших объектах было что-то общее - чего бы вы достигли с помощью наследования, - вам нужно реализовать для них ту же черту.
use std::fmt;
trait Identifier {
fn value(&self) -> &str;
}
Затем мы создаем новый тип, который содержит единственное значение, которое является универсальным типом, который ограничен для реализации нашей черты Identifier
. Самое замечательное в этом шаблоне заключается в том, что он в конце концов будет оптимизирован компилятором.
struct Id<T: Identifier>(T);
Теперь, когда у нас есть конкретный тип, мы реализуем для него черту Display
. Поскольку внутренний объект Id
является Identifier
, мы можем вызвать для него метод value
, поэтому нам нужно реализовать эту черту только один раз.
impl<T> fmt::Display for Id<T>
where
T: Identifier,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.value())
}
}
Ниже приведены определения различных типов идентификаторов и их реализации Identifier
:
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()
}
}
И последнее, но не менее важное: вот как бы вы их использовали:
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);
}
Display
было легко, однако я не думаю, что вы могли бы объединить FromStr
, как показывает мой пример выше, очень вероятно, что разные идентификаторы имеют разные поля, а не только value
(чтобы быть справедливым) у некоторых даже нет value
, в конце концов, черта Identifier
требует, чтобы объект только реализовал метод, называемый value
). И семантически FromStr
должен создать новый экземпляр из строки. Поэтому я бы реализовал FromStr
для всех типов отдельно.