取决于特征的通用实现

Generic implementation depending on traits

定义泛型 struct 时,在 Rust 中是否有一种方法可以根据给定泛型类型实现的特征来使用方法的不同实现 struct =12=]?

例如:

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();
}

有一个名为 specialization 的不稳定功能允许多个 impl 只要其中一个块比另一个块更具体,就可以应用于相同类型的块:

#![feature(specialization)]

struct Printer<T>(T);

trait Print {
    fn print(&self);
}

// specialized implementation
impl<T: fmt::Display> Print for Printer<T> {
    fn print(&self) {
        println!("{}", self.0);
    }
}

// default implementation
impl<T> Print for Printer<T> {
    default fn print(&self) {
        println!("I cannot be printed");
    }
}


struct NotDisplay;

fn main() {
    let not_printable = Printer(NotDisplay);
    let printable = Printer("Hello World");

    not_printable.print();
    printable.print();
}

// => I cannot be printed
// => Hello World

在稳定的 Rust 上,我们需要一些其他机制来完成专业化。 Rust 有另一种语言特性可以做到这一点:method resolution autoref。编译器的规则是,如果可以在没有 autoref 的情况下分派一个方法,那么它就会分派。只有在没有 autoref 的情况下无法分派方法时,编译器才会插入 autoref 并尝试再次解析它。所以在这个例子中:

impl Print for Value {
    fn print(self) {
        println!("Called on Value");
    }
}

impl Print for &Value {
    fn print(self) {
        println!("Called on &Value");
    }
}

Value 的实施将优先于 &Value。知道了这条规则,我们就可以在稳定的 Rust 中模拟特化:

struct Printer<T>(T);

trait Print {
    fn print(&self);
}

// specialized implementation
impl<T: fmt::Display> Print for Printer<T> {
    fn print(&self) {
        println!("{}", self.0);
    }
}

trait DefaultPrint {
    fn print(&self);
}

// default implementation
//
// Note that the Self type of this impl is &Printer<T> and so the 
// method argument is actually &&T! 
// That makes this impl lower priority during method
// resolution than the implementation for `Print` above.
impl<T> DefaultPrint for &Printer<T> {
    fn print(&self) {
        println!("I cannot be printed");
    }
}

struct NotDisplay;

fn main() {
    let not_printable = Printer(NotDisplay);
    let printable = Printer("Hello World");
    
    (&not_printable).print();
    (&printable).print();
}

// => I cannot be printed
// => Hello World

编译器将首先尝试使用 Print 实现。如果不能(因为类型不是 Display),它将使用更通用的 DefaultPrint.

实现

这种技术应用方法解析的方式不能用特征界限来描述,因此它不适用于常规方法,因为我们必须在特征之一(DefaultPrintPrint):

fn print<T: ???>(value: T) {
    (&value).print()
}

然而,这个技巧对于宏非常有用,它不需要拼出特征边界:

macro_rules! print {
    ($e:expr) => {
        (&$e).print()
    };
}


print!(not_printable); // => I cannot be printed
print!(printable); // => Hello World