问题
When defining a generic struct
, is there a way in Rust to use different implementation of a method according to which trait is implemented by the given generic type T
?
For example:
struct S<T> {
value: T,
}
impl<T> S<T> {
fn print_me(&self) {
println!("I cannot be printed");
}
}
impl<T: std::fmt::Display> S<T> {
fn print_me(&self) {
println!("{}", self.value);
}
}
fn main() {
let s = S { value: 2 };
s.print_me();
}
回答1:
There is an unstable feature known as specialization which permits multiple impl
blocks to apply to the same type as long as one of the blocks is "more specific" than the other:
#![feature(specialization)]
struct S<T> {
value: T,
}
trait Print {
fn print_me(&self);
}
// default implementation
impl<T> Print for S<T> {
default fn print_me(&self) {
println!("I cannot be printed");
}
}
// specialized implementation
impl<T: std::fmt::Display> Print for S<T> {
fn print_me(&self) {
println!("{}", self.value);
}
}
struct NotDisplay();
fn main() {
let not_printable = S { value: NotDisplay() };
not_printable.print_me();
let printable = S { value: "hello" };
printable.print_me();
}
// => I cannot be printed
// => hello
To do specialization using only 100% stable and 100% safe code, we'll need some other mechanism to accomplish compile-time fallback through a prioritized sequence of behaviors. As @msrd0 pointed out, Rust has another language feature capable of doing this, which is method resolution autoref as described in this case study. The Rust compilers rule is that if a method can be dispatched without autoref then it will be. Only if a method cannot be dispatched without autoref, the compiler will insert an autoref and attempt to resolve it again. So in this example:
impl Print for Value {
fn print(self) {
println!("called on Value");
}
}
impl Print for &Value {
fn print(self) {
println!("called on &Value");
}
}
The implementation for Value
will be prioritized over &Value
. Knowing this rule, we can mimic specialization in stable Rust:
struct S<T> {
value: T,
}
trait DisplayPrint {
fn print_me(&self);
}
// Priority #1: Specialized impl
impl<T: std::fmt::Display> DisplayPrint for S<T> {
fn print_me(&self) {
println!("{}", self.value);
}
}
trait Print {
fn print_me(&self);
}
// Priority #2: General impl that applies to any T
//
// Note that the Self type of this impl is &T and so the method argument
// is actually &&T! That makes this impl lower priority during method
// resolution than the implementation for `DisplayPrint` above.
impl<T> Print for &S<T> {
fn print_me(&self) {
println!("I cannot be printed");
}
}
macro_rules! print_me {
($($e:expr),*) => {
[$(
(&$e).print_me()
),*]
};
}
struct NotDisplay();
fn main() {
let not_printable = S { value: NotDisplay() };
let printable = S { value: "hello" };
print_me![not_printable, printable];
}
If we run this program, we see that our specialization works:
I cannot be printed
hello
The compiler will try to use the DisplayPrint
implementation first. If it can't (because the type is not Display
), it will then use the more general implementation and output "I cannot be printed".
The way that this technique applies method resolution cannot be described by a trait bound, so it list limited to macros only.
来源:https://stackoverflow.com/questions/64955738/generic-implementation-depending-on-traits