Generic implementation depending on traits

假如想象 提交于 2020-12-23 11:50:48

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!