Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

restricted

Summary

restricted crate (prudent-rs/restricted) prevents consumers of your Rust macros from

  • accidental, or
  • intentional (well-intended or malicious)

unguarded, or corrupt access to your macro's internals. It enables your crates to have private-like variables, functions and types, shared by multiple macros.

That is unlike standard items and variables defined by macros, which, if hygienic, are visible only to the macro that defined them, but not to any successive invocations of macros from the same crate.

restricted makes these variables, functions and types visible also to your other macros, invoked after the invocation that generates them.

What (tl;dr)

restricted crate provides macros that allow you, a macro author, to prevent consumers of your macros from accessing to your declaration statements (that is: items and variables, even if declared with Span::call_site()).

Why?

Macros generate code that blends, or is injected, into the invocator's source code. But

  • macro_rules! are hygienic about let/let mut (and 'labels and $crate). That is good for isolation of local variables. However, it doesn't allow your other macro(s) to access the same variables again.
  • macro_rules! are non-hygienic about any other items they define. Consumers of these macros can access to your macro's internals!
  • proc macros can

How?

restricted generates

  • random-based names for your macro's items and variables, and
  • accessor macros that can be invoked only from your macro(s) and not by the consumer code (by validating the required Span).

restricted provides macros for

  • declaration of (local) let and let mut variables
  • definition of static and const items
    • and both of the above create local macros to access those variables or const or static items
  • local re-export of private items (from a submodule) and
    • macros to access those re-exports by specifying the item's "short name", and
    • "direct" local macros to access those re-exports (one macro per item).

Simple syntax only

There are two reason why restricted crate supports simple syntax only:

  • Supporting full grammar of fn, type, or struct/enum/union or impl definitions (with generics and bounds), would be complicated. Invocations of such macros would be difficult to write. Difficult even to read, or to search in.

    And, migrating existing code would involve extra work.

  • A macro can return a name/path of fn, type and struct/enum/union identifiers. Invocation of such a macro works in Rust in place of the identifier. (In most use cases it works out of the box, and in some it has easy workarounds, like putting the invocation between <...>). For example:

      macro_rules! def_struct_s {
          () => {
              struct S {}
              impl S {
                  const fn new() -> Self {
                      Self {}
                  }
              }
          };
      }
      def_struct_s!();
      
      // accessor macro. This is a simplified example that does not perform Span validation.
      // `restricted` implements the `Span` validation by forwarding to a procedural macro.
      macro_rules! StructS {
          ($token_carrier: tt) => {
              S
          };
      }
    
      const _: StructS!(.) = <StructS!(.)>::new();
    
      pub fn take_value(_: StructS!(.)) {}
      pub fn take_ref(_: &StructS!(.)) {}
      pub fn return_value() -> StructS!(.) {
          todo!()
      }
    
      type StructAlias = StructS!(.);
      const _: StructS!(.) = StructAlias {};

    However, there is no way to apply the same to trait items. For those we have a workaround: use_with.

Simple syntax for let, let mut: no pattern matching in definitions

That's aligned with how const and static work already. They allow only one identifier per each const or static definition. Rust refuses definitions like:

const (_FIRST_CONST,_SECOND_CONST) : (bool, u8) = (true, 1);
static (_FIRST_STATIC, _SECOND_STATIC) : (bool, u8) = (true, 1);

Why supporting let and let mut

Any let and let mut variables generated by macro_rules (and by proc macros with Span::mixed_site() are hygienic, so they can't be accessed from outside (of the macro that created them).

However, such let and let mut variables then can't be accessed from other macro(s), even if those other macro(s) are from the same crate (the crate which created the macro that created the variable).

Blockers and related issues

Please give thumbs up (and contribute, if you can) to

UTF-8 and Unicode

Requiring UTF-8

Source file paths have be in UTF-8.

Normally non-ASCII Unicode-friendly

If you use an unicameral alphabet (which doesn't differentiate between lowercase and uppercase), you may need to indicate the naming convention (one of: CamelCase, lower_case or UPPER_CASE).

That's because (obviously) restricted then can't detect the naming convention. restricted needs the convention choice for generating the random part (which is always in ASCII, to make it easier to maintain).

You can include unicameral letters and use auto-detection if use also include some bicameral letters and/or an underscore, and if those clearly imply the convention.

NOT watt-compatible

NOT compatible with dtolnay/watt (because of side effects of build.rs).

About

Prevent consumers of your Rust macros from accidents. Enable your crates to export private-like types, traits and functions that are to be used only by its own `macro_rules` (from the same crate).

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages