Custom attribute to run macro generated code

I think there is a use case for an attribute that just like tests, can be used to call rust functions scattered all over your code, but without testing purposes.

Use cases

My main motivating example for this use case is a crate I've been working on called models (still under development though). it can be used to generate and update an SQL schema from your structs. It works by generating one test every time you derive Model in a struct. This way, when running cargo test, it can be used to update your SQL migrations.

I also noticed safer_ffi does a similar thing. It uses tests to generate c headers from your rust code.

It would be nice if there was a built-in feature for this purpose.

Idea

We could have some attribute that marks a function to be executed on a particular cargo command like this:

#[helper_fn(some_namespace)]
fn foo() {
   /* ... */
}

then we can call cargo helper some_namespace and have it run all functions in that namespace.

Benefits

Your dependencies would also be allowed to add functions to a namespace, which is currently not possible with tests.

note: this is just a quick sketch of an idea I just had.

1 Like

What problem does this solve? I understand what it can do, but I'm having difficulty understanding what problem can be solved that needs solved.

Currently tests are being used to execute rust code for code generation purposes (SQL, c headers, and possible others). This would provide a built in way to support that instead of a hack. I know though, that the current way works, so in a way the need for this is weaker. A benefit of this would be that it would allow dependencies to also take part in this generation, which is not possible with tests. I can't think of any use cases for this to be honest, so I understand if you think this is a useless feature.

Why would you not use build.rs for generating code? That is its purpose, no?

ts-rs also uses tests created by derive macros to generate TypeScript files from Rust types. I wrote a ts-rs inspired crate elm_rs which just exposes an export function that the user can call as they wish. It's more manual work for the user of the crate but using tests for code generation feels too much like a hack to me. I'm not sure how I'd improve the situation though.

3 Likes

Counterexample: rust-analyzer does its code generation in tests now.[1] The reason for this is that sometimes generated code is heavy to generate, and you don't want to impose that generation cost onto users who want to install the crate. Rust-analyzer commits its generated files into source control and checks that they're up to date on ~every cargo test, which is required for this scheme to work. Consider a generation step that requires heavy source file processing (e.g. parsing and processing the UCD); you don't necessarily want to push all of those files to everyone, when you can ship preprocessed source files.

It's not necessarily always the best solution, but it has its benefits. And if cargo natively supported something serving this need, we could theoretically have the benefits of both worlds. (That'd be a bigger feature than OP's ask, though, I think.)


  1. It used to be behind cargo xtask codegen or similar, but was pushed into cargo test to be more automatic. The test fails with effectively a "rerun to bless" message, and codegen is checked on CI by the tests. They do the same thing for cargo fmt, even! ↩ī¸Ž

4 Likes

Aha, I understand now. Thanks for the clear explanation.