32

Is there a way to print out a complete list of available members of a type or instance in Rust?

For example:

  • In Python, I can use print(dir(object))
  • In C, Clang has a Python API that can parse C code and introspect it.

Being unfamiliar with Rust tools, I'm interested to know if there is some way to do this, either at run-time or compile-time, either with compiler features (macros for example), or using external tools.

This question is intentionally broad because the exact method isn't important. It is common in any language to want to find all of a variable's methods/functions. Not knowing Rust well, I'm not limiting the question to specific methods for discovery.

The reason I don't define the exact method is that I assume IDEs will need this information, so there will need to be some kinds of introspection available to support this (eventually). For all I know, Rust has something similar.

I don't think this is a duplicate of Get fields of a struct type in a macro since this answer could include use of external tools (not necessarily macros).

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • 2
    Why do you want to do that? Do you just want to inspect the content or do you want some kind of programmatic access? As it is the question is very broad and we already have an answer that is only useful to humans while another answer could (maybe, with effort) allow programmatic access. – Matthieu M. Sep 01 '16 at 14:14
  • 3
    @ideasman42: I am sorry if I was not clear, I am asking *how* you want to exploit the information. Consulting the docs is sufficient for a human, but is not good if you want to programmatically iterate over the attributes to serialise the object. What you may want to do with it constrains the forms in which the information should be provided. – Matthieu M. Sep 01 '16 at 14:56
  • I started a bounty and would like to explain: `rustdoc` does not do what I would want. I don't want introspection on things *I've* created, but others have. E.g. I want to know how I can create a `Vec` from the values in a [`HashMap`](https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html). After enough Google searches, it seems I can `my_map.values().iter().collect()`, but I'm not even sure if that's right, and the documentation for `HashMap` does not really help. The decoupling of *behavior* from *data* makes it hard to know how these relationships work, IMHO. – Mike Williamson May 30 '19 at 04:33
  • 1
    @MikeWilliamson you've poorly chosen the question for your bounty then. You are effectively asking "how do I go from arbitrary type A to arbitrary type B" and that doesn't even account for "shortest path" or "most efficient path". There is no single method on either `Vec` or `HashMap` to convert between the two, so any answer to *this question* won't help you. – Shepmaster May 30 '19 at 13:54
  • 1
    @MikeWilliamson Even your specific example doesn't have enough detail. You are getting a `Vec` of *references* to your values, not the values themselves. That may or may not be what you actually want. If you wanted the values themselves, that would be `my_map.into_iter().map(|(_, v)| v).collect()`. – Shepmaster May 30 '19 at 13:57
  • @Shepmaster Yes, I understand your point. I only provided a *specific* example, but the issue is more broad, and the OP posed a broad question: how do we introspect? I accept that traits and impls might be better vs. classes in some key ways that I am still learning. But **gone** is the ability to introspect at all. There *must* be a way. Or, if not, how can we collectively build a way? My question was more circuitous, I admit, but that's because my point was that deep introspection allows for this indirect path. Sometimes, I am happy with *a* solution until I can make *the best one*. – Mike Williamson May 31 '19 at 17:52

7 Answers7

24

Is there a way to print out a complete list of available members of a type or instance in Rust?

Currently, there is no such built-in API that you can get the fields at runtime. However you can retrieve fields by using two different ways.

  1. Declarative Macros
  2. Procedural Macros

Solution By Using Declarative Macro

macro_rules! generate_struct {
    ($name:ident {$($field_name:ident : $field_type:ty),+}) => {
        struct $name { $($field_name: $field_type),+ }
        impl $name {
            fn introspect() {
            $(
            let field_name = stringify!($field_name);
            let field_type = stringify!($field_type);
               println!("Field Name: {:?} , Field Type: {:?}",field_name,field_type);
            )*
            }
        }
    };
}

generate_struct! { MyStruct { num: i32, s: String } }

fn main() {
    MyStruct::introspect();
}

This will give you the output:

Field Name: "num" , Field Type: "i32"
Field Name: "s" , Field Type: "String"

Playground


Solution Using Procedural Macro

Since procedural macros are more complicated from the declarative macros, you better to read some references(ref1, ref2, ref3) before starting.

We are going to write a custom derive which is named "Instrospect". To create this custom derive, we need to parse our struct as a TokenStream with the help of syn crate.

#[proc_macro_derive(Introspect)]
pub fn derive_introspect(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as ItemStruct);

    // ...
}

Since our input can be parsed as ItemStruct and ItemStruct has the fields() method in it, we can use this to get fields of our struct.

After we get these fields, we can parse them as named and we can print their field name and field type accordingly.

input
    .fields
    .iter()
    .for_each(|field| match field.parse_named() {
        Ok(field) => println!("{:?}", field),
        Err(_) => println!("Field can not be parsed successfully"),
    });

If you want to attach this behavior to your custom derive you can use the following with the help of the quote crate:

let name = &input.ident;

let output = quote! {
    impl #name {
        pub fn introspect(){
            input
            .fields
            .iter()
            .for_each(|field| match field.parse_named() {
                Ok(field) => println!("{:?}", field),
                Err(_) => println!("Field can not be parsed successfully"),
             });
        }
    }
};

// Return output TokenStream so your custom derive behavior will be attached.
TokenStream::from(output)

Since the behaviour injected to your struct as introspect function, you can call it in your application like following:

#[derive(Introspect)]
struct MyStruct {
    num: i32,
    text: String
}

MyStruct::introspect();

Note: Since the example you are looking for similar to this question. This Proc Macro Answer and Declarative Macro Answer should give you insight as well

vallentin
  • 23,478
  • 6
  • 59
  • 81
Akiner Alkan
  • 6,145
  • 3
  • 32
  • 68
  • 7
    How does this address the **main** part of the question: *How to introspect all available **methods***? – Shepmaster May 31 '19 at 14:15
  • 1
    How does this work when another procedural macro *adds or removes* fields? – Shepmaster May 31 '19 at 14:16
  • This should be the accepted answer. This is the solution Rust provides and is exactly what how it is used. Serialization/deserialization is the most common use of introspection/reflection and in Rust, macros fill that roll. – Bernard Igiri Aug 21 '20 at 16:25
  • Does this really works? I found ```error[E0425]: cannot find value `input` in this scope --> tests/macros.rs:6:14 | 6 | #[derive(Introspect, Debug)] | ^^^^^^^^^^ not found in this scope``` – Alexandre Mahdhaoui Sep 25 '22 at 11:52
  • @AlexandreMahdhaoui it should not. Please look my answer: https://stackoverflow.com/a/74573771/5397119 – Sergio Ivanuzzo Nov 25 '22 at 13:46
7

To expand on my comment, you can use rustdoc, the Rust documentation generator, to view almost everything you're asking for (at compile time). rustdoc will show:

  • Structs (including public members and their impl blocks)
  • Enums
  • Traits
  • Functions
  • Any documentation comments written by the crate author with /// or //!.

rustdoc also automatically links to the source of each file in the [src] link.

Here is an example of the output of rustdoc.

Standard Library

The standard library API reference is available here and is available for anything in the std namespace.

Crates

You can get documentation for any crate available on crates.io on docs.rs. This automatically generates documentation for each crate every time it is released on crates.io.

Your Project

You can generate documentation for your project with Cargo, like so:

cargo doc

This will also automatically generate documentation for your dependencies (but not the standard library).

Aurora0001
  • 13,139
  • 5
  • 50
  • 53
1

I have written a very simple crate which uses procedural macro. It gives you access to members information plus some simple information about struct/enum you use. Information about methods can not be given because procedural macros simply can't get this information, and as far as I know, there are no any methods which may give such information.

VP.
  • 15,509
  • 17
  • 91
  • 161
1

According to question of @AlexandreMahdhaoui, I would say: at least on latest Rust versions the proc_macro from accepted answer will not work, because you will need to pass tokens into quote! using "#". So you could try smth like next:

use proc_macro::{TokenStream};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, ItemStruct};

#[proc_macro_derive(Introspect)]
pub fn derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as ItemStruct);
    let ident = input.ident;

    let field_data = input.fields.iter().map(|f| {
        let field_type = f.ty.clone();
        format!(
            "Name={}, Type={}",
            f.ident.clone().unwrap().to_string(),
            quote!(#field_type).to_string()
        )
    }).collect::<Vec<_>>();

    let output = quote! {
        impl #ident {
            pub fn introspect() {
                println!("{:#?}",  vec![#(#field_data),*]);
            }
        }
    };

    TokenStream::from(output)
}

#[derive(Introspect)]
struct Test {
    size: u8,
    test_field: u8,
}

fn main() {
    Test::introspect();
}

Regarding methods, defined in impl I didn't find any info in output, so not sure if it possible. Probably somebody could share in comments ?

Sergio Ivanuzzo
  • 1,820
  • 4
  • 29
  • 59
0

I don't think there is anything that will do this out of the box.

It may be possible to write a compiler plugin which can do that by examining the AST.

Chris Emerson
  • 13,041
  • 3
  • 44
  • 66
  • 3
    This is far from trivial at AST level. This is why even builtin things like `derive(Clone)` or `println!` can give really bad errors. At HIR level (accessible from a lint plugin), types and bindings have been resolved, and things would be much easier. – mcarton Sep 01 '16 at 09:58
0

If you need the field names inside your program then you probably need to use macros. Either wrap your struct definition in macro and pattern match to create some function to get their names, or use procedural macro to derive structs for traits with such functions.

See examples in syn for derived traits. In particular, see syn::Data::Struct which has fields.

Keji Li
  • 62
  • 1
  • 7
-1

I use something like this:

println!("{:?}", variable); // struct, enum whatever

If it's a large type, use the # version:

println!("{:#?}", variable); // struct, enum whatever
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
zzeroo
  • 5,466
  • 4
  • 33
  • 49