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.
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
#[crate_interface::def_interface]
pub trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
// Implement the interface in any crate
struct HelloIfImpl;
#[crate_interface::impl_interface]
impl HelloIf for HelloIfImpl {
fn hello(&self, name: &str, id: usize) -> String {
format!("Hello, {} {}!", name, id)
}
}
// Call `HelloIfImpl::hello` in any crate
use crate_interface::call_interface;
assert_eq!(
call_interface!(HelloIf::hello("world", 123)),
"Hello, world 123!"
);
assert_eq!(
call_interface!(HelloIf::hello, "rust", 456), // another calling style
"Hello, rust 456!"
);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
#[crate_interface::def_interface(gen_caller)]
pub trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
// a function to call the interface function is generated here like:
// fn hello(name: &str, id: usize) -> String { ... }
// Implement the interface in any crate
struct HelloIfImpl;
#[crate_interface::impl_interface]
impl HelloIf for HelloIfImpl {
fn hello(&self, name: &str, id: usize) -> String {
format!("Hello, {} {}!", name, id)
}
}
// Call the generated caller function using caller function
assert_eq!(
hello("world", 123),
"Hello, world 123!"
);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.
mod a {
#[crate_interface::def_interface(namespace = ShoppingMall)]
pub trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
}
mod b {
#[crate_interface::def_interface(namespace = Restaurant)]
pub trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
}
mod c {
use super::{a, b};
struct HelloIfImplA;
#[crate_interface::impl_interface(namespace = ShoppingMall)]
impl a::HelloIf for HelloIfImplA {
fn hello(&self, name: &str, id: usize) -> String {
format!("Welcome to the mall, {} {}!", name, id)
}
}
struct HelloIfImplB;
#[crate_interface::impl_interface(namespace = Restaurant)]
impl b::HelloIf for HelloIfImplB {
fn hello(&self, name: &str, id: usize) -> String {
format!("Welcome to the restaurant, {} {}!", name, id)
}
}
}
fn main() {
// Call the interface functions using namespaces
assert_eq!(
crate_interface::call_interface!(namespace = ShoppingMall, a::HelloIf::hello("Alice", 1)),
"Welcome to the mall, Alice 1!"
);
assert_eq!(
crate_interface::call_interface!(namespace = Restaurant, b::HelloIf::hello("Bob", 2)),
"Welcome to the restaurant, Bob 2!"
);
}A few things to keep in mind when using this crate:
- 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.
The procedural macros in the above example will generate the following code:
// #[def_interface]
pub trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
#[allow(non_snake_case)]
pub mod __HelloIf_mod {
use super::*;
extern "Rust" {
pub fn __HelloIf_hello(name: &str, id: usize) -> String;
}
}
struct HelloIfImpl;
// #[impl_interface]
impl HelloIf for HelloIfImpl {
#[inline]
fn hello(&self, name: &str, id: usize) -> String {
{
#[inline]
#[export_name = "__HelloIf_hello"]
extern "Rust" fn __HelloIf_hello(name: &str, id: usize) -> String {
let _impl: HelloIfImpl = HelloIfImpl;
_impl.hello(name, id)
}
}
{
format!("Hello, {} {}!", name, id)
}
}
}
// call_interface!
assert_eq!(
unsafe { __HelloIf_mod::__HelloIf_hello("world", 123) },
"Hello, world 123!"
);If you enable the gen_caller option in def_interface, calling helper
functions will also be generated. For example, HelloIf above will generate:
pub trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub mod __HelloIf_mod {
use super::*;
extern "Rust" {
pub fn __HelloIf_hello(name: &str, id: usize) -> String;
}
}
#[inline]
pub fn hello(name: &str, id: usize) -> String {
unsafe { __HelloIf_mod::__HelloIf_hello(name, id) }
}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:
pub trait HelloIf {
fn hello(&self, name: &str, id: usize) -> String;
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub mod __HelloIf_mod {
use super::*;
extern "Rust" {
pub fn __ShoppingMall_HelloIf_hello(name: &str, id: usize) -> String;
}
}