Crate mod_interface

Source
Expand description

§Module :: mod_interface

experimental rust-status docs.rs discord

Provides the mod_interface! macro to define structured module interfaces with controlled visibility and propagation, simplifying the creation of layered architectures in Rust.

§Overview

The mod_interface crate introduces a procedural macro (mod_interface!) designed to streamline module organization in Rust projects. It helps address common challenges in maintaining complex codebases:

  1. Structured Interfaces: Define clear boundaries and relationships between modules (layers) using predefined exposure levels. This promotes a layered architecture where visibility and propagation of items are explicitly controlled.
  2. Reduced Boilerplate: The macro automatically generates the necessary use statements and module structures based on simple directives, reducing manual effort and potential errors.
  3. Improved Readability: By encouraging the explicit definition of a module’s interface and how its items are exposed, the crate helps make the codebase easier to understand, navigate, and refactor, reducing cognitive load.

It offers a convention-based approach to modularity, particularly useful for designing complex systems where clear structure and controlled visibility are paramount.

§Basic Concepts

In the mod_interface crate, the concepts of layers and namespaces are central to its modularity approach. Here’s a refined explanation:

  • Namespaces: These are standard Rust modules that help organize code into logical groups.
  • Layers: A layer is a specialized module structured using mod_interface!. It contains a set of predefined submodules, referred to as Exposure Levels, which dictate how the contents of the module are propagated to parent layers.

The Exposure Levels within a layer determine the visibility and propagation scope:

LevelPropagation ScopePurpose
privateInternal onlyOriginal definitions
ownLayer only (no propagation)Layer-specific public items
orphanImmediate parentItems for direct parent
exposedAll ancestorsItems for hierarchy use
preludeAll ancestors + intended globCore interface essentials (glob use)

Developers should define all entities within the private submodule and then re-export them through the other four exposure levels (own, orphan, exposed, prelude) based on the desired propagation strategy.

§Syntax of mod_interface Macro

The mod_interface macro provides several directives to manage the relationships between layers and entities:

  • layer <name>: Define and include <name>.rs (or <name>/mod.rs) as a child layer within the current module.
  • use <path>: Integrate an existing module at <path> as a layer into the current module’s interface.
  • reuse <path>: Similar to use, integrates an existing module layer, potentially with slightly different propagation rules intended for reusing common interfaces.
  • <level> use <item>: Re-export <item> (from private or elsewhere) into the specified exposure level (own, orphan, exposed, or prelude).
  • <level> mod <name>: Define <name>.rs (or <name>/mod.rs) as a “micro module” and include its contents directly into the specified exposure level.

These directives provide flexibility in organizing and managing the modular structure of a Rust program, enhancing both readability and maintainability.

§Example: Using Layers and Entities

This example shows a parent module using a child layer, demonstrating how items propagate based on their assigned exposure level.

For a module to be used as a layer, it must contain the necessary exposure levels (private, own, orphan, exposed, prelude). The mod_interface! macro helps generate these.

use mod_interface::mod_interface;

// Define a module named `child`.
pub mod child
{

  // Define a private namespace for all its items.
  mod private
  {
    /// Only my thing. (Will be in `own`)
    pub fn my_thing() -> bool
    {
       true
    }
    /// Parent module should also has this thing. (Will be in `orphan`)
    pub fn orphan_thing() -> bool
    {
       true
    }
    /// This thing should be exposed. (Will be in `exposed`)
    pub fn exposed_thing() -> bool
    {
       true
    }
    /// This thing should be in prelude. (Will be in `prelude`)
    pub fn prelude_thing() -> bool
    {
       true
    }
  }

  // Use mod_interface to define the exposure levels for child's items
  crate::mod_interface!
  {
    own use my_thing;
    orphan use orphan_thing;
    exposed use exposed_thing;
    prelude use prelude_thing;
  }

}

// Parent module also needs a private namespace.
mod private {}

// Parent module uses the `child` layer.
crate::mod_interface!
{
  /// Use the child layer.
  use super::child;
}


// fn main() // Example usage demonstrating visibility:
{

  // `prelude_thing` is in `prelude`, so it propagates everywhere.
  assert!( child::prelude_thing(), "prelude thing of child is there" );
  assert!( prelude_thing(), "Accessible in parent's root via prelude propagation" );
  assert!( own::prelude_thing(), "Accessible in parent's own via prelude propagation" );
  assert!( orphan::prelude_thing(), "Accessible in parent's orphan via prelude propagation" );
  assert!( exposed::prelude_thing(), "Accessible in parent's exposed via prelude propagation" );
  assert!( prelude::prelude_thing(), "Accessible in parent's prelude via prelude propagation" );

  // `exposed_thing` is in `exposed`, propagates to all ancestors except their prelude.
  assert!( child::exposed_thing(), "exposed thing of child is there" );
  assert!( exposed_thing(), "Accessible in parent's root via exposed propagation" );
  assert!( own::exposed_thing(), "Accessible in parent's own via exposed propagation" );
  assert!( orphan::exposed_thing(), "Accessible in parent's orphan via exposed propagation" );
  assert!( exposed::exposed_thing(), "Accessible in parent's exposed via exposed propagation" );
  // assert!( prelude::exposed_thing(), "but not in parent's prelude" ); // Fails

  // `orphan_thing` is in `orphan`, propagates only to the immediate parent's root and `own`.
  assert!( child::orphan_thing(), "orphan thing of child is there" );
  assert!( orphan_thing(), "Accessible in parent's root via orphan propagation" );
  assert!( own::orphan_thing(), "Accessible in parent's own via orphan propagation" );
  // assert!( orphan::orphan_thing(), "but not in parent's orphan" ); // Fails
  // assert!( exposed::orphan_thing(), "and not in parent's exposed" ); // Fails
  // assert!( prelude::orphan_thing(), "and not in parent's prelude" ); // Fails

  // `my_thing` is in `own`, does not propagate.
  assert!( child::my_thing(), "own thing of child is only there" );
  // assert!( my_thing(), "and not here" ); // Fails
  // assert!( own::my_thing(), "and not here" ); // Fails
  // assert!( orphan::my_thing(), "and not here" ); // Fails
  // assert!( exposed::my_thing(), "and not here" ); // Fails
  // assert!( prelude::my_thing(), "and not here" ); // Fails

}
Click to see the code expanded by the macro
use mod_interface::mod_interface;

// Define a module named `child`
pub mod child
{
  // Define a private namespace for all its items.
  mod private
  {
    /// Only my thing. (Will be in `own`)
    pub fn my_thing() -> bool
    {
       true
    }
    /// Parent module should also has this thing. (Will be in `orphan`)
    pub fn orphan_thing() -> bool
    {
       true
    }
    /// This thing should be exposed. (Will be in `exposed`)
    pub fn exposed_thing() -> bool
    {
       true
    }
    /// This thing should be in prelude. (Will be in `prelude`)
    pub fn prelude_thing() -> bool
    {
       true
    }
  }

  // Use mod_interface to define the exposure levels for child's items
  /* crate::mod_interface! { own use my_thing; orphan use orphan_thing; exposed use exposed_thing; prelude use prelude_thing; } */
  // Expanded code generated by the macro:
  pub use own::*;
  /// Own namespace of the module.
  pub mod own
  {
      use super::*;
      pub use orphan::*;
      pub use private::my_thing;
  }
  /// Orphan namespace of the module.
  pub mod orphan
  {
      use super::*;
      pub use exposed::*;
      pub use private::orphan_thing;
  }
  /// Exposed namespace of the module.
  pub mod exposed
  {
      use super::*;
      pub use prelude::*;
      pub use private::exposed_thing;
  }
  /// Prelude to use essentials: `use my_module::prelude::*`.
  pub mod prelude
  {
      use super::*;
      pub use private::prelude_thing;
  }

}

// Parent module also needs a private namespace.
mod private {}

// Parent module uses the `child` layer.
/* crate::mod_interface! { use super::child; } */
// Expanded code generated by the macro:
pub use own::*;
/// Own namespace of the module.
#[ allow( unused_imports ) ]
pub mod own
{
    use super::*;
    pub use orphan::*;
    #[ doc( inline ) ]
    #[ allow( unused_imports ) ]
    #[ doc = " Use the child layer."]
    pub use super::child::orphan::*; // Items from child's orphan are pulled into parent's own
    #[ doc( inline ) ]
    #[ allow( unused_imports ) ]
    #[ doc = " Use the child layer."]
    pub use super::child; // The child module itself is available in parent's own
}
/// Orphan namespace of the module.
#[ allow( unused_imports ) ]
pub mod orphan
{
    use super::*;
    pub use exposed::*;
    // Child's orphan items do not propagate to parent's orphan
}
/// Exposed namespace of the module.
#[ allow( unused_imports ) ]
pub mod exposed
{
    use super::*;
    pub use prelude::*;
    #[ doc( inline ) ]
    #[ allow( unused_imports ) ]
    #[ doc = " Use the child layer."]
    pub use super::child::exposed::*; // Items from child's exposed are pulled into parent's exposed
}
/// Prelude to use essentials: `use my_module::prelude::*`.
#[ allow( unused_imports ) ]
pub mod prelude
{
    use super::*;
    #[ doc( inline ) ]
    #[ allow( unused_imports ) ]
    #[ doc = " Use the child layer."]
    pub use super::child::prelude::*; // Items from child's prelude are pulled into parent's prelude
}


// fn main() // Example usage demonstrating visibility:
{

  // `prelude_thing` is in `prelude`, so it propagates everywhere.
  assert!( child::prelude_thing(), "prelude thing of child is there" );
  assert!( prelude_thing(), "Accessible in parent's root via prelude propagation" );
  assert!( own::prelude_thing(), "Accessible in parent's own via prelude propagation" );
  assert!( orphan::prelude_thing(), "Accessible in parent's orphan via prelude propagation" );
  assert!( exposed::prelude_thing(), "Accessible in parent's exposed via prelude propagation" );
  assert!( prelude::prelude_thing(), "Accessible in parent's prelude via prelude propagation" );

  // `exposed_thing` is in `exposed`, propagates to all ancestors except their prelude.
  assert!( child::exposed_thing(), "exposed thing of child is there" );
  assert!( exposed_thing(), "Accessible in parent's root via exposed propagation" );
  assert!( own::exposed_thing(), "Accessible in parent's own via exposed propagation" );
  assert!( orphan::exposed_thing(), "Accessible in parent's orphan via exposed propagation" );
  assert!( exposed::exposed_thing(), "Accessible in parent's exposed via exposed propagation" );
  // assert!( prelude::exposed_thing(), "but not in parent's prelude" ); // Fails

  // `orphan_thing` is in `orphan`, propagates only to the immediate parent's root and `own`.
  assert!( child::orphan_thing(), "orphan thing of child is there" );
  assert!( orphan_thing(), "Accessible in parent's root via orphan propagation" );
  assert!( own::orphan_thing(), "Accessible in parent's own via orphan propagation" );
  // assert!( orphan::orphan_thing(), "but not in parent's orphan" ); // Fails
  // assert!( exposed::orphan_thing(), "and not in parent's exposed" ); // Fails
  // assert!( prelude::orphan_thing(), "and not in parent's prelude" ); // Fails

  // `my_thing` is in `own`, does not propagate.
  assert!( child::my_thing(), "own thing of child is only there" );
  // assert!( my_thing(), "and not here" ); // Fails
  // assert!( own::my_thing(), "and not here" ); // Fails
  // assert!( orphan::my_thing(), "and not here" ); // Fails
  // assert!( exposed::my_thing(), "and not here" ); // Fails
  // assert!( prelude::my_thing(), "and not here" ); // Fails

}

§Debugging

To debug module interface use directive #![ debug ] in macro mod_interface. Let’s update the main file of the example :

mod_interface::mod_interface!
{
  #![ debug ]
  /// Inner.
  layer child; // Or `use super::child;` if defined separately
}

Full sample see at sample directory.

§To add to your project

cargo add mod_interface

§Try out from the repository

git clone https://github.com/Wandalen/wTools
cd wTools
cd examples/mod_interface_trivial
cargo run

§Try out from the repository

git clone https://github.com/Wandalen/wTools
cd wTools
cd examples/mod_interface_trivial
cargo run

Modules§

dependency
Namespace with dependencies.
exposed
Exposed namespace of the module.
meta
orphan
Orphan namespace of the module.
own
Own namespace of the module.
prelude
Prelude to use essentials: use my_module::prelude::*.

Macros§

mod_interface
Protocol of modularity unifying interface of a module and introducing layers.