2

I want to match, e.g. an ident's type to implement a certain trait, how would I do that?

Here the basic idea in (incomplete) code:

macro_rules! has_trait {
    ($ ($t : ty), ($x : ident),) => {

    } 
}

fn trait_test() {
    let a = vec![1, 2, 3];
    let b = 42;
    let a_iteratable = has_trait!(IntoIterator, a);
    let b_iteratable = has_trait!(IntoIterator, b);
    println!("{:?} iterable? {}", a, a_iteratable);
    println!("{:?} iterable? {}", b, b_iteratable);
}

I cannot wrap my head around how to say "any type which has trait Foo".

I see 2 options how to tackle the problem:

  1. Find a match expression which matches any type with trait $t and simply return true on match, else (how works else?) false.
  2. In the body of the match of any type, use some code to determine if trait $t is implemented by the type of $x.

I cannot see how to do either of both options.

Can this even be done?

Chris Emerson
  • 13,041
  • 3
  • 44
  • 66
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • I'd recommend [edit]ing your question to try to explain what you are trying to do. If you want "any type which has trait `Foo`", that's just normal generic types, no need for macros: `fn bar(val: T)`. – Shepmaster Jun 15 '16 at 01:14
  • I think I do explain what I try to do: Find the match expression in a macro which matches "any type with trait foo". – BitTickler Jun 15 '16 at 01:43
  • I would add a few versions of rephrasing the question, were by some mystery I not unable to edit my own question... – BitTickler Jun 15 '16 at 01:52
  • It's mostly unclear **why** you want to do this. If you try to pass a type that doesn't implement a trait to a function that requires it, the compiler will tell you. What good is having a boolean that tells you the same information? – Shepmaster Jun 15 '16 at 02:51
  • As for the "why", here 2 scenarios: 1. I wanted to write macros to create strings for various data types as a soft warm-up exercise with Rust macro system. Clearly a IntoIterator type would have different representation as a integral value etc.). 2. Imagine you want to specify to someone else what the type he is supposed to write for you is supposed to support. In the initial unit test, you could write "specification" like ``has_trait!(IntoIterator,Foo);...`` and send the guy off to work, them knowing what is expected. – BitTickler Jun 15 '16 at 02:59
  • I don't understand why (1) would care about if it is a specific trait or not, as a type can implement multiple traits; a boolean for one trait doesn't seem useful. Really you'd just use something like [`TypeId`](http://doc.rust-lang.org/std/any/struct.TypeId.html). For (2), you would write basically what I wrote earlier, no need for macros: `#[test] fn test1() { fn is_into_iterator() {}; is_into_iterator::() }` – Shepmaster Jun 15 '16 at 03:06
  • @Shepmaster Cannot help you not liking my why. But then - I mostly care about the "how" and "possible". – BitTickler Jun 15 '16 at 03:07

3 Answers3

4

I am afraid there is here a serious misconception about what macros can and cannot do.

In Rust, a macro acts on the AST, short for Abstract Syntax Tree. This means that it has access to syntactic information (only).

It means that anything that a macro does, you can also do without a macro. A macro is just syntactic sugar to avoid writing boilerplate over and over.

And conversely, if you cannot do something without a macro, you cannot do it with a macro either.

It is not immediately clear to me whether this information is available or not (proving a negative is always so difficult), however it is certain that the usage of macros has no influence on this availability.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    The fact that things like: ``ty, ident, ...`` are known might contribute to this misconception. So it knows already the token is a type, an identifier,... but that is where it probably ends. – BitTickler Jun 16 '16 at 06:31
  • @BitTickler: Ah yes, Rust has a regular grammar (hopefully LL(1)), to the *kind* of the entity is known without further analysis because at that point it can only be a type, identifier, expression, ... however note that `ty` is used interchangeably for both Type and Trait, and that you can perfectly use a non-existing type for it without issue (at this stage). From the point of view of the *syntax*, a type is a type, even if it was never defined. – Matthieu M. Jun 16 '16 at 09:22
2

As the other answers have already made clear, there is nothing a macro can do. And indeed, in current (stable) Rust, that's it. However, if you are willing to either use nightly or wait until specialization is stable, you can write and implement a trait to make that distinction, e.g.

#[feature(specialization)] // nightly only for now

trait HasMyTrait {
    fn has_trait() -> bool;
}

impl<T> HasMyTrait for T {
    default fn has_trait() -> bool { false }
}

impl<T: MyTrait> HasMyTrait for T {
     fn has_trait() -> bool { true }
}

This is just a simple example, but you can switch out multiple implementations of whatever functionality you want based on if the type in question implements a trait or not.

This code requires Rust 1.11.0 nightly as of 2016-06-02 or newer.

llogiq
  • 13,815
  • 8
  • 40
  • 72
  • Presumably that is specific to each trait (or set of traits) that you'd want to check, so you could then use a macro to make constructing these 9 lines of code easier. – Shepmaster Jun 15 '16 at 16:01
  • 2
    Of course, but it was hard enough to type this version on my mobile phone. ;-) – llogiq Jun 15 '16 at 16:14
  • do note that as of my comment here, specialization is actually unsound, and can produce unsafe behavior with safe code. – lahwran Dec 07 '20 at 19:21
  • I should probably update the code to use autoref-based specialization then. It is both sound and available on stable. – llogiq Dec 09 '20 at 06:15
1

What you basically want is static (or compile-time) reflection: Assigning values at compile-time, depending on the type system, to use at run-time. This is possible in for example D or even C++, but not in Rust.

Rust does not allow template specialisation or compile-time values as generic parameters, nor does it have static reflection capabilities like D.

JDemler
  • 1,246
  • 14
  • 22
  • 2
    Note: Rust *plans* to allow compile-time values as generic parameters (maybe limited to integers to start with). Compile-time reflection (as required by serialization frameworks) is performed via plugins, instead (only available on nightly) and not formally part of the language; there is no plan to integrate it further in the language as far as I know. – Matthieu M. Jun 16 '16 at 09:26