How to match on concrete types of generic struct f

2019-07-29 02:49发布

问题:

I'm playing around with the lambda calculus, but since I'm not too generics-savvy, I would like to confirm my understanding of some things and resolve an error. Consider the following definitions:

pub trait Term {} // a lambda term - a variable, an abstraction or an application

pub struct Var(pub usize); // a variable, De Bruijn style
pub struct Abs<T: Term>(T); // an abstraction
pub struct App<T: Term, U: Term>(T, U); // application of T on U

I understand (i.e. otherwise it doesn't work) that I need App to be generic over <T: Term, U: Term> as opposed to just <T: Term> to be able to e.g. apply a Var to an App, i.e. to have an App(Var(x), App(...)).

The aforementioned structs are all Terms:

impl Term for Var {}
impl<T: Term> Term for Abs<T> {}
impl<T: Term> Term for App<T, T> {}

Interesting that I don't need App<T, U> here, but hopefully so far so good - now I would like to implement fmt::Display for the aforementioned structs:

use std::fmt;
use std::fmt::Display;

impl fmt::Display for Var {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<T: Term+Display> Display for Abs<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "λ{}", self.0)
    }
}

These two impls work just fine; I'm including them since the next one relies on them:

impl<T: Term+Display> Display for App<T, T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            App(Var(_), Var(_)) => write!(f, "{}{}", self.0, self.1),
            _ => unimplemented!()
        }
    }
}

But it fails with:

error[E0308]: mismatched types
  --> src\ast.rs:34:8
   |
34 |                    App(Var(_), Var(_)) => write!(f, "{}{}", self.0, self.1),
   |                        ^^^^^^ expected type parameter, found struct `ast::Var`
   |
   = note: expected type `T`
   = note:    found type `ast::Var`

I would like to print App differently based on the types of its contents. I tried to find a related question, but they mostly revolve around associated types. Is there a simple solution or do I have to rework the definitions?

回答1:

I would like to print App differently based on the types of its contents.

That's easy enough, just implement Display for every unique type of App you wish to be able to print:

use std::fmt;

struct App<T, U>(T, U);

impl fmt::Display for App<i32, bool> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Awesome choice!")
    }
}

impl fmt::Display for App<bool, String> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Woah, a string? Really?")
    }
}

fn main() {
    println!("{}", App(42i32, false));
    println!("{}", App(true, "wow".to_string()));
}

You can also accept further types that have their own generics:

impl<T> fmt::Display for App<Vec<T>, char> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} of them; {}", self.0.len(), self.1)
    }
}

Note that this has no unimplemented! call; everything is checked at compile time:

error[E0277]: the trait bound `App<bool, bool>: std::fmt::Display` is not satisfied
  --> src/main.rs:24:24
   |
24 |         println!("{}", App(true, false));
   |                        ^^^^^^^^^^^^^^^^ the trait `std::fmt::Display` is not implemented for `App<bool, bool>`
   |
   = note: `App<bool, bool>` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
   = note: required by `std::fmt::Display::fmt`

This has a close analog in the standard library: Cursor. This type accepts a generic, but all of the interesting functionality is only implemented for a handful of concrete types like &[u8] or Vec<u8>.

but it still causes trouble, e.g. with App<Var, App>, because the inner App expects 2 type arguments again

Yes, you have to specify the generics because App isn't a type, it's only a stepping stone towards one.

It all depends on what you want to do. The simplest is to just have an App composed of two types of any kind, so long as you don't use them:

impl<T, U> fmt::Display for App<i32, App<T, U>> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Semi-awesome")
    }
}

If you want to be able to display the App, then you need to restrict the generics such that App is displayable:

impl<T, U> fmt::Display for App<i32, App<T, U>>
    where App<T, U>: fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}) squared!", self.1)
    }
}

Check it out with

fn main() {
    let a = App(42i32, false);
    println!("{}", a);
    let b = App(100, a);
    println!("{}", b);
    let c = App(100, b);
    println!("{}", c);
}

I have a guess that the follow up question will be something about having some kind of fallback or default case for all the non-special conditions. Something like:

impl fmt::Display for App<i32, bool> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Awesome choice!")
    }
}

impl<T: fmt::Display, U: fmt::Display> fmt::Display for App<T, U> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Boring: {} + {}", self.0, self.1)
    }
}

Nope, that won't compile! i32 and bool also implement Display so it is ambiguous which implementation to select. At this point, you are into the realm of specialization. This forces you to truly understand the orphan rules.

As far as I understand the current implementation of specialization, you cannot specialize on a concrete type, only on traits.



标签: generics rust