#module #cross-platform #compile-time #static

nightly macro def-mod

Simplifies module implementation routing and statically verifies exports

5 releases (breaking)

0.5.0 Jul 31, 2019
0.4.0 Dec 21, 2018
0.3.0 Dec 20, 2018
0.2.0 Dec 20, 2018
0.1.0 Dec 19, 2018

#2206 in Procedural macros

MIT license

24KB
361 lines

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.


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:

fn method(_: u32) -> u8;

// into

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

A method assertion with generics is a bit more complex:

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:

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:

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:

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;
    }
}

Dependencies

~2MB
~48K SLoC