"macro-local" names, with name mangling as DRM

it'd be cool if std or core provided a macro_local! procmacro that mangled names.

so you'd do something like this:

fn foo() {}
const BAR: Foo = Foo;

macro_local! {
  use foo;
  use BAR;
  macro_rules! whatever {
    ...
  }
}

and the macro would take the "use" and re-export them as "pub use", but with name mangling as DRM. so if you look at the symbol table it'd look something like this:

foo_123432151416517177985975792123
BAR_096098409585727468245123145843

and the only way to figure these out is by using the macro directly, as these would be CSPRNGed when the macro_local macro is called.

The standard way to do this would be

#[doc(hidden)] /// Not public API!
mod _macro_reexports {
    pub use crate::{foo, BAR};
}

macro_rules! {
    ...
    $crate::_macro_reexports::foo();
    ...
}

or by just making the macro-used APIs documented and available.

Or by consulting rustdoc, if the reƫxports aren't marked #[doc(hidden)], as I assume you implicitly intended. After all, the intent is that these names are only available to the macro, but not downstream code.

The "real" solution is "macros 2.0", or "truly hygenic macros," or whatever you want to call it. With those you'd just write

fn foo() { ... }

pub macro whatever() {
    ...
    foo();
    ...
}

and names would be resolved in the scope of the macro def-site, not the call-site, so it'd be able to use the local foo here.

Of course, this macro style is a long, long way off, so we'll be using just macro_rules! (macros 1.0) and proc_macro (macros 1.1 (item position) and 1.2 (expression position*)) for a while yet.

* IIRC; I've lost track of the numeric "code names" for the various macro capabilities.

3 Likes

just turn

pub fn foo() {}

macro_rules! {
  foo()
}

into

pub fn foo501275032897480917() {}

macro_rules! {
  foo501275032897480917()
}

at compile-time, but every time the crate is compiled it generates a new unguessable number.

so even if you did look at the docs you'd still not be able to use it because it just gets regenerated under a different name on the next compilation attempt anyway. and then your build fails. and even if you do get it to work consistently on your machine, it would never work on someone else's machine, or on a new machine, or on a build server, or whatnot.

also heck this can be done today as an external crate.

What you're asking for is already done by default to all names. Rust already mangles names to implement crate and module scoping. foo() function is not called foo in the executable. It will be something like foo_832ad8a987af9d87a. That's why there's #[no_mangle].

For a "DRM" you can use strip on the built executable to remove all public symbols. This is even better than obfuscated symbols.

but then how do you use the macro?

the idea is to use the macro but not the function called by the macro: you either use the macro or your code only has a 1 in 2^256 chance of compiling, per compilation attempt.

OK, I don't think I really understand what you're trying to accomplish here. Could you try and explain that in some more detail?

1 Like

I assume the OP wants something like gensym? I'm not sure it's accurate to call this "DRM", and I think using that term is confusing here.

You can't. The pub fn foo() {} item is outside the macro. If you want the macro to manipulate something, then you must pass it to the macro. Macros can't modify code outside of them.

If you do pass pub fn foo() {} into the macro, it's pretty easy to manipulate the identifier however you want. I do this in one of my crates: I need to generate symbol names for many static items, which the linker can then merge together based on some stuff. But reusing symbol names causes the linker to emit duplicate symbol errors, so I just append a random number to the end of each symbol.

I can't think of anything here that requires new features in Rust. It's already doable on stable.

In that case this should be done in an external crate and should not be added to Rust until that crate has proven the value of such a utility. Many of Rust's new features start out as external crates. This should probably also start as an external crate.

1 Like

[off]

I love this spelling.

[/off]

7 Likes

the point is you'd put the whole thing inside a macro like "macro_local! {}" - both the macro_rules and the pub fn. it's DRM in the sense of preventing internal methods from being practical to call outside of using the macro directly.

Wouldn't this prevent reproducible builds from ever happening?

3 Likes

DRM always has its tradeoffs. that's no excuse not to use DRM tho.

Do you see any problem with adding a new visibility level like pub(macro) instead of using random symbol names? That would fix the problem while still guaranteeing reproducible builds.

Am I the only one to have noticed that this thread is just a request to implement macro hygiene for items? Has everyone been fooled by the misleading naming and implementation proposal?

(I might also be completely mistaken, of course.)

3 Likes

I think this is slightly different than hygiene, since it's about calling from macro-generated code to items defined outside the macro.

Many crates today use #[doc(hidden)] for this purpose. For example the futures::poll! macro depends on these public-but-undocumented items. But nothing technically prevents downstream crates from using these unsupported APIs directly. The proposal here is one possible solution to this problem.

2 Likes

Exactly. It's one possible solution, and can be implemented today! That's the best part really, it can be implemented outside of core but would be better if implemented in core to encourage ppl to use it.

On the other hand, let's be honest: we hate this. we think it's a bad idea. that's why we're calling it "DRM" and so on. it's a horrible hack and it does break reproducible builds, and probably a bunch of other things as well. (not sure it would break incremental compilation as well but it might.)

We're sorry.

Sorry, Soni, but there's one problem with the "DRM" naming/description. I'm not sure whether you're aware of that, but in a lot of jurisdictions DRM has (too) strong legal protections.

When you're calling this DRM, do you intend it to be used as such? Do you mean that linking to this function anyways with some hack should be illegal and punishable with huge fines and jail time? If that's not the case, I'd suggest not using the confusing terminology.

2 Likes

Companies use APIs. If it was illegal to use them, companies would have a strong incentive to push back against DRM being legally binding. It's kinda the perfect way to get rid of DRM while also adding an useful language feature! gross API visibility hack!