Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jezza/def_mod

A proc macro that simplifies implementation routing and statically verifies module exports.
https://github.com/jezza/def_mod

compile-time implementation-routing macros modules rust

Last synced: 7 days ago
JSON representation

A proc macro that simplifies implementation routing and statically verifies module exports.

Awesome Lists containing this project

README

        

`def_mod!` provides a familiar syntax to the standard module declarations, but with the added benefit
of simpler implementation routing and statically verified module exports.

---
```rust
extern crate def_mod;

use def_mod::def_mod;

def_mod! {
// This has the exact same behaviour as Rust's.
mod my_mod;

// Much like the one above, it also has the same effect as Rust's.
mod my_first_mod {
// This will check if a method with the name `method` and type `fn(u32) -> u8` was exported.
// It will fail to compile if it finds none.
fn method(_: u32) -> u8;

// Much like the method declaration from above, this will check to see if a type was exported.
type MyStruct;

// Much like the normal method check, that functionality is also extended to types.
// So you can check if a type has a specific method exported.
type MyOtherStruct {
// This will check if this method exists on this type. (MyOtherStruct::method)
fn method(_: u32) -> u8;
}
}

// You can declare attributes like normal.
#[cfg(windows)]
mod my_second_mod;

// When declaring an attribute, you can optionally add a string literal.
// This literal is used as the path attribute for the module file.
// All attributes declared with a path are treated as _mutually exclusive_.
// So a `mod` declaration is generated for each.
// This makes it a lot easier to manage cross-platform code.
// Note: attributes that don't have a path are copied to each module declaration.

#[cfg(windows)] = "sys/win/mod.rs"
#[cfg(not(windows))] = "sys/nix/mod.rs"
mod sys;

// Expands to:

#[cfg(windows)]
#[path = "sys/win/mod.rs"]
mod sys;

#[cfg(not(windows))]
#[path = "sys/nix/mod.rs"]
mod sys;

// You can also declare attributes on methods or types themselves, and they will be used when verifying the type.
// This module itself will be verified when not on a windows system.
#[cfg(not(windows))]
mod my_third_mod {
// This method will only be verified when on linux.
#[cfg(linux)]
fn interop() -> u8;
// Same with this type. It will only be verified when on a macos system
#[cfg(macos)]
type SomeStruct {
fn interop() -> u8;
}
}
}

fn main() {
}
```

NOTE: that `def_mod` uses syntax tricks to assert for types.
So that means when you use the path shorthand, it will still only check the module that is loaded, not all potential modules.

One good example is when you have two different modules for specific platforms.
The module that would be compiled normally is the only one that would be checked.
You would need to explicitly enable compilation of the modules if you want them to be checked too.
Because you can declare arbitrary #[cfg] attributes, there's no generic way for `def_mod` to know how to do this.
It's entirely up to you.

---

In case you're curious as to what the macro generates:

A method assertion is transformed to something like:

```rust
fn method(_: u32) -> u8;

// into

const _VALUE: fn(u32) -> u8 = my_mod::method;
```

A method assertion with generics is a bit more complex:

```rust
fn generic<'a , T: 'a>(_: u32, _: T, _: fn(T) -> T) -> &'a T;

// will turn into into (Note: This is nested inside of the load function itself.)

#[allow(non_snake_case)]
fn _load_module_name_generic<'a, T: 'a>() {
let _VALUE:
fn(_: u32, _: T,
_: fn(T) -> T) -> &'a T =
other::generic;
}
```

A type assertion is transformed into a new scope with a use declaration:

```rust
def_mod! {
mod my_mod {
type Test;
}
}

// Into

{
use self::my_mod::Test;
// Any method assertions for the type will also be placed inside the same scope.
}
```

All of those are shoved into a function generated by the macro.

For example, given something like:

```rust
def_mod! {
mod my_mod {
fn plus_one(value: u8) -> u8;

type MyStruct {
fn new() -> Self;
fn dupe(&self) -> Self;
fn clear(&mut self);
}

fn generic<'a , T: 'a>(_: u32, _: T, _: fn(T) -> T) -> &'a T;
}
}
```

It'll turn it into something like this:

```rust
mod my_mod;

fn _load_my_mod() {
const _ASSERT_METHOD_0: fn(u8) -> u8 = self::my_mod::plus_one;
{
use self::my_mod::MyStruct;
const _ASSERT_METHOD_1: fn() -> MyStruct = MyStruct::new;
const _ASSERT_METHOD_2: fn(_self: &MyStruct) -> MyStruct = MyStruct::dupe;
const _ASSERT_METHOD_3: fn(_self: &mut MyStruct) -> MyStruct = MyStruct::clear;
}
#[allow(non_snake_case)]
fn _load_my_mod_generic<'a, T: 'a>() {
let _ASSERT_METHOD_4:
fn(_: u32, _: T,
_: fn(T) -> T) -> &'a T =
my_mod::generic;
}
}
```