Skip to main content

Crate mlua_pkg

Crate mlua_pkg 

Source
Expand description

§mlua-pkg

Composable Lua module loader built in Rust.

§Design philosophy

Lua’s require("name") is a name -> value transformation. This crate defines that transformation as a composable abstraction, allowing multiple sources (memory, filesystem, Rust functions, assets) to be handled uniformly.

§Resolution model

§Abstractions

ConceptTypeRole
Resolution unitResolvername -> Option<Result<Value>>
Composition (Chain)RegistryResolvers in priority order, first match wins
Composition (Prefix)resolvers::PrefixResolverStrip prefix and delegate to inner Resolver

Resolvers come in two kinds: leaf (directly produce values) and combinator (compose other Resolvers). Both implement the same Resolver trait, enabling infinite composition.

§Leaf Resolvers

ResolverSourceMatch condition
resolvers::MemoryResolverHashMap<String, String>Name is registered
resolvers::NativeResolverFn(&Lua) -> Result<Value>Name is registered
resolvers::FsResolverFilesystemFile exists
resolvers::AssetResolverFilesystemKnown extension + file exists

§Combinators

CombinatorBehavior
Registry (Chain)Try [R1, R2, ..., Rn] in order, adopt first Some
resolvers::PrefixResolver"prefix.rest" -> strip prefix -> delegate "rest" to inner Resolver

§Resolution flow

require("name")
  |
  v
package.searchers[1]  <- Registry inserts its hook here
  |
  +- Resolver A: resolve(lua, "name") -> None (not responsible)
  +- Resolver B: resolve(lua, "name") -> Some(Ok(Value)) (first match wins)
  |
  v
package.loaded["name"] = Value  <- Lua standard require auto-caches

§Return value protocol

Return valueMeaningNext Resolver
NoneNot this Resolver’s responsibilityTried
Some(Ok(value))Resolution succeededSkipped
Some(Err(e))Responsible but load failedSkipped

Some(Err) intentionally does not fall through to the next Resolver. If a module was “found but broken”, having another Resolver return something different would be a source of bugs.

§Naming conventions

Name patternExampleResponsible Resolver
@scope/name@std/httpresolvers::NativeResolver – exact name match
prefix.namegame.engineresolvers::PrefixResolver -> delegates to inner Resolver
dot.separatedlib.helperresolvers::FsResolverlib/helper.lua
name.extconfig.jsonresolvers::AssetResolver – auto-convert by extension

resolvers::FsResolver converts dot separators to path separators (lib.helper -> lib/helper.lua). resolvers::AssetResolver treats filenames literally (config.json -> config.json). The two naturally partition by the presence of a file extension.

§Composition example

Registry (Chain)
+- NativeResolver            @std/http  -> factory(lua)
+- Prefix("sm", FsResolver)  sm.helper  -> strip -> helper.lua
+- FsResolver(root/)         sm         -> sm/init.lua
|                             lib.utils  -> lib/utils.lua
+- AssetResolver              config.json -> parse -> Table

resolvers::PrefixResolver acts as a namespace mount point. require("sm") (init.lua) is handled by the outer resolvers::FsResolver, while require("sm.helper") is handled by resolvers::PrefixResolver. Responsibilities are clearly separated.

§Usage

use mlua_pkg::{Registry, resolvers::*};
use mlua::Lua;

let lua = Lua::new();
let mut reg = Registry::new();

// 1st: Rust native modules (highest priority)
reg.add(NativeResolver::new().add("@std/http", |lua| {
    let t = lua.create_table()?;
    t.set("version", 1)?;
    Ok(mlua::Value::Table(t))
}));

// 2nd: Embedded Lua sources
reg.add(MemoryResolver::new().add("utils", "return { pi = 3.14 }"));

// 3rd: Filesystem (sandboxed)
reg.add(FsResolver::new(&plugins)?);

// 4th: Assets (register parsers explicitly)
reg.add(AssetResolver::new(&assets)?
    .parser("json", json_parser())
    .parser("sql", text_parser()));

reg.install(&lua)?;

// Lua side: require("@std/http"), require("utils"), etc.

§Lua integration

Registry::install() inserts a hook at the front of Lua’s package.searchers table. It takes priority over the standard package.preload, so registered Resolvers are tried first.

Caching is delegated to Lua’s standard package.loaded. On the second and subsequent require calls for the same module, Lua’s cache hits and the Resolver is not invoked.

§Error design

Error typeWhen raisedDefined in
ResolveErrorDuring resolve() executionThis module
sandbox::InitErrorDuring FsSandbox::new() constructionsandbox
sandbox::ReadErrorDuring SandboxedFs::read()sandbox

By separating construction-time and runtime errors at the type level, callers can choose the appropriate recovery strategy.

Modules§

resolvers
Resolver implementations: leaves and combinators.
sandbox
Sandboxed file I/O abstraction and implementation.

Structs§

LuaConvention
Configuration bundle for Lua dialect naming conventions.
Registry
Chain combinator for Resolver. Registration order = priority order. First match wins.

Enums§

ResolveError
Error type for module resolution.

Traits§

Resolver
Minimal abstraction for module resolution.