1

I'm creating a macro that parses a user-generated trait and creates helper functions for it. I have type restrictions on some of the arguments used, so I've been following the example in the quote crate to generate some code that'll throw a compilation error if a trait isn't implemented. It's not perfect but it gets the job done:

struct _AssertSync where #ty: Sync;

I'd also like to be able to check if the trait has Hash as a supertrait. If I follow the same pattern, I get an error:

struct _AssertHash where dyn #tr: Hash;

A complete example:

use std::hash::Hash;

trait BadBoi {}
trait GoodBoi: Hash {}

struct _AssertHash
where
    dyn GoodBoi: Hash;
error[E0038]: the trait `GoodBoi` cannot be made into an object
   --> src/lib.rs:6:39
    |
4   | trait GoodBoi: Hash {}
    |       ------- this trait cannot be made into an object...
5   | 
6   | struct _AssertHash where dyn GoodBoi: Hash;
    |                                       ^^^^ the trait `GoodBoi` cannot be made into an object
    |
    = help: consider moving `hash` to another trait

Is there any way for me to get around this?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Alastair
  • 5,894
  • 7
  • 34
  • 61

1 Answers1

4

You can check a supertrait-relationship by using two functions, for example:

fn _assert_hash_supertrait<T: $tr>() {
    fn requires_hash<T: Hash>() {}
    let _ = requires_hash::<T>;
}

This only compiles if the bound T: $tr implies T: Hash. And the only way that's the case if Hash is a super-trait of $tr. Also see this helpful Q&A.

Full example (Playground):

trait BadBoi {}
trait GoodBoi: Hash {}

macro_rules! foo {
    ($tr:ident) => {
        fn _assert_hash_supertrait<T: $tr>() {
            fn requires_hash<T: Hash>() {}
            let _ = requires_hash::<T>;
        }
    }
}

//foo!(GoodBoi);
foo!(BadBoi);

Of course in this solution, calling foo! twice errors because assert_hash_supertrait is defined twice, but solving that is not part of this question.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • Amazing, thank you. I added a little tweak: adding `use _DummyName as $tr`, and then `fn _assert_hash_supertrait<$tr: _DummyName>`. That way the error message that gets shown to the user has the correct identifier attached (there's no way to use custom error messages, right?!) – Alastair Nov 13 '20 at 19:38
  • 1
    @Alastair Custom error messages: not in this case. This kind of super-trait error always has the same message. You can emit custom messages in some cases (e.g. via [`compile_error!()`](https://doc.rust-lang.org/std/macro.compile_error.html)), but that won't help you here. – Lukas Kalbertodt Nov 13 '20 at 19:48