Rust dev and
UI/UX designer

<- MyBlog

11th August, 2022

5 min read

Debugging proc macros using croc-look


The terminal solution right now for debugging procedural macros in rust is called cargo-expand which is a good solution but what if,

  • You want a declutered, straight to the point view of what your code is generating
  • You want to see the output of your macro as your writing it (live reloading)

The reason why I think croc-look is a good solution is that it allows you to think straight in terms of the code your generating.

croc-look will help the community better debug proc macros and make them tiny bit easier to make.

Getting started is as easy as

cargo install croc-look

Let's take a look at this


#[derive(Serialize, Debug, Clone)]
struct T {}

Suppose your the person who's writing the derive macro Serialize. Your implementing the Serialize trait for a struct. Now you want to see the whole impl.


➜ croc-look -t Serialize -i T

Is enough to find the impl since there's only one impl in the scope right now. Let's look at edge cases


#[derive(Serialize, Debug, Clone)]
struct T {}

#[derive(Serialize, Debug, Clone)]
struct B {}

mod module {
    use super::*;

    #[derive(Serialize, Debug, Clone)]
    struct C(usize);

    #[derive(Serialize, Debug, Clone)]
    struct D(u64);
}

The challenge is to look at Serialize impl for each struct we see in the code above. Let's look in the module first


➜ croc-look -p module --trait-impl Serialize -i C

This shows you the impl of Serialize in the module module for struct C. Works the same with nested modules


mod module {
    use super::*;

    #[derive(Serialize, Debug, Clone)]
    struct C(usize);

    #[derive(Serialize, Debug, Clone)]
    struct D(u64);

    mod inner_module {
        use super::*;

        #[derive(Serialize, Debug, Clone)]
        struct E(u64);
    }
}

The command croc-look -p module::inner_module --trait-impl Serialize -i E outputs


impl _serde::Serialize for E {
    fn serialize<__S>(&self, __serializer: __S) -> _serde::__private::Result<__S::Ok, __S::Error>
    where
        __S: _serde::Serializer,
    {
        _serde::Serializer::serialize_newtype_struct(__serializer, "E", &self.0)
    }
}

This will work without -p module::inner_module assuming the struct names aren't the same across modules but it'll be faster to execute if you specify the module since croc-look will only parse that module.

The --watch flag

The watch feature is gonna come in handy when your working on big macros. It features a TUI which allows you to see your code live

The left side shows the --watch flag of croc-look, right part shows me working on a proc-macro = true crate

Once you're able to form the command which shows you the macro you want, you are able to use the --watch flag to watch a particular directory recursively or even a single file.

Hope this makes proc macros a little bit easier for you!


Links: