crate_interface
Provides a way to define a static interface (as a Rust trait) in a crate, implement it in another crate, and call it from any crate, using procedural macros. This is useful when you want to solve circular dependencies between crates.
Example
Basic Usage
Define an interface using the def_interface! attribute macro, implement it
using the impl_interface! attribute macro, and call it using the
call_interface! macro. These macros can be used in separate crates.
// Define the interface
// Implement the interface in any crate
;
// Call `HelloIfImpl::hello` in any crate
use call_interface;
assert_eq!;
assert_eq!;
Generating Calling Helper Functions
It's also possible to generate calling helper functions for each interface
function, so that you can call them directly without using the call_interface!
macro.
This is the RECOMMENDED way to use this crate whenever possible, as it provides a much more ergonomic API.
// Define the interface with caller generation
// a function to call the interface function is generated here like:
// fn hello(name: &str, id: usize) -> String { ... }
// Implement the interface in any crate
;
// Call the generated caller function using caller function
assert_eq!;
Avoiding Name Conflicts with Namespaces
You can specify a namespace for the interface to avoid name conflicts when
multiple interfaces with the same name are defined in different crates. It's
done by adding the namespace argument to the def_interface!,
impl_interface! and call_interface! macros.
Default Implementations with Weak Symbols
The weak_default feature allows you to define default implementations for
interface methods. These defaults are compiled as weak symbols, which means:
- Implementors can choose to override only the methods they need
- Methods without explicit implementations will automatically use the defaults
- The linker resolves which implementation to use at link time
This is useful when you want to provide sensible defaults while still allowing
customization. To use this feature, you need to use nightly Rust and enable
#![feature(linkage)] in the crate that defines the interface trait.
Due to Rust compiler limitations, it's impossible to implement an interface
with default implementations in the same crate where it's defined. This should
not be a problem for most cases, because the only sensible scenario where an
interface would be implemented in the same crate where it's defined is for
testing, and such tests can always be done in a separate crate.
For example, given the following interface definition with default implementations:
use def_interface;
The macro will expand to:
The default implementation is compiled as a weak symbol (#[linkage = "weak"]).
When an implementor provides their own implementation using impl_interface, it
generates a strong symbol with the same name, which the linker will prefer over
the weak symbol.
When a default implementation calls another trait method via Self::method(),
a proxy function (__self_proxy_method) is generated. This proxy calls the
extern function, ensuring that if an implementor overrides that method, the
overridden (strong symbol) version is called at runtime instead of the default.
Things to Note
A few things to keep in mind when using this crate:
-
Methods with receivers are not supported. Interface functions must not have
self,&self, or&mut selfparameters. Use associated functions (static methods) instead:# use *; -
Generic parameters are not supported. Interface functions cannot have generic type parameters, lifetime parameters, or const generic parameters:
# use *; -
Do not implement an interface for multiple types. No matter in the same crate or different crates as long as they are linked together, it will cause a link-time error due to duplicate symbol definitions.
-
Do not define multiple interfaces with the same name, without assigning them different namespaces.
crate_interfacedoes not use crates and modules to isolate interfaces, only their names and namespaces are used to identify them. -
Do not alias interface traits with
use path::to::Trait as Alias;, only use the original trait name, or an error will be raised.
Implementation
The procedural macros in the above example will generate the following code:
// #[def_interface]
;
// #[impl_interface]
// call_interface!
assert_eq!;
If you enable the gen_caller option in def_interface, calling helper
functions will also be generated. For example, HelloIf above will generate:
Namespaces are implemented by further mangling the symbol names with the
namespace, for example, if HelloIf is defined with the ShoppingMall
namespace, the generated code will be: