Creating code to be run through macro but not directly

I'm not even sure that I can describe what I want clear enough, but I'll try.

In my lib, there is a procedural macro reexported through the main part and some struct with the private field in the main part. The proc-macro is an attribute, which is placed by the user on some function; the function is then converted to the macro_rules definition, which, being called in the expression position like the original function would, should yield the struct from the main crate.

The problem is that I don't want users of the lib to create the instances of this struct in any other way: only through this proc-macro. But is it ever possible? Or, if some code is used in macro expansion, then the same code would work at the same place being simply written by hand?

1 Like

You can't do that, unfortunately. Unless you expand the macro to a function call, and that function creates your type. Macros called outside of your crate can only access your public api.

You can make a public API with #[doc(hidden)] attribute and a name like _DONT_TOUCH_THIS_.

2 Likes

That's indeed what is customary to do.

If you are gonna use unsafe code elsewhere that relies on invariants that should be upheld by that struct (since it is expected to have been created by your macro), then you need to mark the function constructor unsafe (and make your macro expand to unsafe code).

In any case, you cannot be held responsible for bugs that may occur from someone using a #[doc(hidden)] pub struct __DO_NOT_USE__. For instance, if you change both the macro and the struct in a way that keeps them working, but where __DO_NOT_USE__ now has a new field, then such change would not require a major semver change.

5 Likes

I suppose if you were really concerned, you could use some proc-macro trickery to change the name of the secret function on each build. That should prevent any unscrupulous users from trying to use this undocumented and secret API... But it would also seem like a nice idea for the language to have a mechanism to do this for us.

1 Like

Yes, at the end it's all about invariants (which are upheld by generated code, but not guaranteed if user creates the struct himself). Thanks, looks like this was the missing link.

1 Like