Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finding out if type implements a particular trait #77

Closed
rushmorem opened this issue Jan 16, 2017 · 12 comments
Closed

Finding out if type implements a particular trait #77

rushmorem opened this issue Jan 16, 2017 · 12 comments
Labels

Comments

@rushmorem
Copy link

rushmorem commented Jan 16, 2017

Say I have a type Foo. Is it possible to find out if it implements a certain trait Bar? In my particular case I want to find out if a certain type implements std::error::Error in a macros 1.1 macro.

/cc @dtolnay

@dtolnay
Copy link
Owner

dtolnay commented Jan 16, 2017

No it's not possible in a procedural macro. During macro expansion the only thing that exists is the syntax tree of your program (basically the text of your program) and that is the only thing macros, whether procedural or macro_rules, are operating on. The compiler has not put together type information about anything yet, doesn't know what type a particular name refers to, doesn't know what crate that type might come from, doesn't even know whether something with that name exists. All of that is figured out after macro expansion is done.

Can you explain a bit more about your use case? There might be an alternative way to accomplish the same thing.

@rushmorem
Copy link
Author

rushmorem commented Jan 16, 2017

I'm working on a new crate to derive errors using Macros 1.1. As you already know, error types must implement std::error::Error which has the methods description and cause. The cause method must optionally return a reference to an underlying std::error::Error.

Instead of requiring users to submit only objects that implement std::error::Error as part of their error payload, I want to allow them to submit an arbitrary object. If that object implements std::error::Error then I will defer to its methods, if not, I will return None for cause.

The reason I want to do it this way is that I have noticed that quite a few libraries do not implement std::error::Error for their error types. I also have an idea I'm working on that will make error handling in Rust even nicer if this works out.

@dtolnay
Copy link
Owner

dtolnay commented Jan 16, 2017

I think we can make this work using specialization, although that feature is still unstable. The idea would be something like this:

trait AsError {
    fn as_error(&self) -> Option<&Error>;
}

impl<T> AsError for T {
    default fn as_error(&self) -> Option<&Error> { None }
}

impl<T> AsError for T where T: Error {
    fn as_error(&self) -> Option<&Error> { Some(self) }
}

Then if you have a variable x, your generated code can use AsError::as_error(&x) to get either None or Some(&x) as appropriate.

@rushmorem
Copy link
Author

Thanks for the tip. I will probably create a nightly feature that uses specialization for this. Any idea on how I can make this work on stable? I will play around and see if I can get an alternative that can work on v1.15.0.

I typically code against stable because I plan to use the libraries I'm working on in production very soon. The only reason I'm using procedural macros now is that they will be in v1.15.0 which is already around the corner.

@rushmorem
Copy link
Author

trait AsError {
    fn as_error(&self) -> Option<&Error>;
}

impl AsError for Error {
    fn as_error(&self) -> Option<&Error> { Some(self) }
}

impl<T> AsError for T {
    fn as_error(&self) -> Option<&Error> { None }
}

That compiles fine on stable but when I try it within quote! I'm running into the following error:-

error: recursion limit reached while expanding the macro `stringify`
   --> src/error.rs:78:9
    |
78  |         quote! {
    |         ^
    |
    = note: this error originates in a macro outside of the current crate

I tried increasing recursion limit to 1024 (#![recursion_limit = "1024"]) to no avail. Do you think I'm onto something with this or it's just a waste of time?

@rushmorem
Copy link
Author

If I move that out into the crate that's calling derive it does compile. However, I seem to be getting None all the time.

@dtolnay
Copy link
Owner

dtolnay commented Jan 17, 2017

Yeah that code is different from the code using specialization. I think you need specialization to make this work ergonomically, can't think of a way to do it on stable. You would need the user to indicate to you in some way whether or not the thing implements std::error::Error, possibly using an attribute like these. Then you can generate different code for the two cases.

In my version I have an impl returning None that applies to all Sized types T, and I have an impl returning Some that applies to all Sized types T where T implements Error. Note that there are some types for which both of these impls apply, so we need specialization in order for the compiler to tell which one "applies more" in some sense.

In your version you have an impl returning None that applies to all Sized types T, and an impl returning Some that applies to the dynamically sized (unsized) type Error which is the trait object associated with the Error trait. There are no types for which both of these impls apply, which is why it works without specialization and on stable. You can actually get it to return Some if you have a trait object: https://is.gd/qRK7JH but this is not what you want.

@rushmorem
Copy link
Author

Thanks again. I think attributes would be good enough for now. How does one define their own attributes on stable? I can't seem to find any documentation for it.

@dtolnay
Copy link
Owner

dtolnay commented Jan 17, 2017

Here is an example from the heapsize_derive crate: https://github.com/servo/heapsize/blob/v0.3.8/derive/lib.rs#L13

#[proc_macro_derive(HeapSizeOf, attributes(ignore_heap_size_of))]

This defines the #[derive(HeapSizeOf)] custom derive and allows it to use the #[ignore_heap_size_of] attribute.

The attributes will be part of the syntax tree you get when you parse the input to your macro. They can show up in a few different places depending on where in the input the user wrote them.

#[my_attr] // syn::MacroInput::attrs
enum E {
    #[my_attr] // syn::Variant::attrs
    V {
        #[my_attr] // syn::Field::attrs
        f: u8
    }
}

Here is where the heapsize_derive crate loops over field attributes: https://github.com/servo/heapsize/blob/v0.3.8/derive/lib.rs#L23-L33

For now attributes can be pretty tedious to work with but in #25 we have been thinking of ways to make this better.

@rushmorem
Copy link
Author

Awesome. I can't thank you enough for all the help you have given me and your patience.

As for defining attributes, I can't believe it's that simple. Which reminds me... Where can I find the documentation for the proc_macro crate? When I started looking into procedural macros I looked everywhere for it until I finally gave up.

@dtolnay
Copy link
Owner

dtolnay commented Jan 17, 2017

I don't think it is available officially yet. Manish is hosting it here for now: https://manishearth.github.io/rust-internals-docs/proc_macro/

@rushmorem
Copy link
Author

Sweet! Thanks a lot!

Repository owner locked and limited conversation to collaborators Jul 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants