Using a String as a type name in macros [solved]


#1

Hello,

is there any way to use a String/&str as a type using a macro (whether macro_rules! macro or plugins type of macro)?

To put words in code, how can I create a macro that would accept something like the last expression compile & run?

trait SayHello {
    fn hello_world() {
        println!("Hello world !!!");
    }    
}
struct HelloWorld;
impl SayHello for HelloWorld {}

macro_rules! say_hello {
    ($generic:tt) => {
        <$generic>::hello_world();
    }
}

fn say_hello_typed<T: SayHello>() {
    say_hello!(T);
}

fn main() {
    say_hello_typed::<HelloWorld>();
    say_hello!(HelloWorld);
    //say_hello!("HelloWorld"); <-- how can I make this compile and run like the above?
}

Playground link

Thanks!!!


(edited to add Playground link)


#2

This isn’t really possible in a statically compiled language without reflection.

I’m assuming you’re wanting to use a string so that you can change which function you call at runtime. However to generate the machine code for your program, the compiler needs to know which function to call at compile time.

Also, in macros a string is interpreted as an expression, whereas you’d need an identifier or path in order to call the method corresponding to the type with that name. By the time your macro sees that string, it doesn’t know it’s a string any more, it just knows it’s some kind of expression so it could be 5+2 for all it knows.

If you are already using traits then it’s still quite possible to choose which concrete type gets used at run-time. You just need to do things in a different way.


#3

I’m trying to have the following logic work

fn build<T: ?Sized + 'static>() -> Box<T> {
    // some logic that would ensure that HelloWorld implements T
    let object : Box<T> = Box::new(HelloWorld{});
    object
}

fn main() {
    let object = build::<SayHello>();
    assert_eq!(object.say_hello(), "Hello world !!!".to_string());
}

The overall objective being to build a generic dependency injection framework. Essentially you’re right, not for function though but for trait. I’m trying to change the implementation of a trait at runtime, based on a component which one would have registered as concrete implementation of that trait at startup (SOLID logic).

In the spirit of the build() being part of a generic library I can’t type T and the approach I tried was through trait objects.

Sorry if unclear :slight_smile:


#4

Well I managed to find a way with plugins.

It is ungly, unsafe, and I wouldn’t recommend using that outside the playground (and even there …) but here is what worked for me

in say_hello.rs test file

#![feature(plugin)]
#![plugin(test_plugin)]

trait SayHello {
    fn hello_world() -> String {
        "Hello world !!!".to_string()
    }    
}
struct HelloWorld;
impl SayHello for HelloWorld {}

// Impl for variant #1
macro_rules! static_callback {
    ($callback:tt::$method:tt) => {
        $callback::$method()
    };
}

// Impl for variant #2
macro_rules! say_hello_typed {
    ($generic:ty) => {
        <$generic>::hello_world()
    }
}

fn say_hello_typed<T: SayHello>() -> String {
    say_hello_typed!(T)
}

// Impl for variant #3

#[test]
fn say_hello_basics() {
    assert_eq!(<HelloWorld>::hello_world(), "Hello world !!!".to_string());
}

#[test]
fn say_hello_v1() {
    assert_eq!(static_callback!(HelloWorld::hello_world), "Hello world !!!".to_string());
} 

#[test]
fn say_hello_v2() {
    assert_eq!(say_hello_typed::<HelloWorld>(), "Hello world !!!".to_string());
}

#[test]
fn say_hello_v3() {
    let result = say_hello!("HelloWorld");
    println!("Result from plugin = {}", &result);
    assert_eq!(result, "Hello world !!!".to_string());
}

And the plugin code from lib.rs

#![crate_type = "dylib"]
#![feature(plugin_registrar, quote, rustc_private)]

extern crate rustc;
extern crate rustc_plugin;
extern crate syntax;

use rustc_plugin::Registry;
use syntax::ast::Ident;
use syntax::codemap;
use syntax::tokenstream;
use syntax::ext::base::{ExtCtxt, MacResult, MacEager, DummyResult};
use syntax::parse::token::{ Lit, Token };

/// Compiler hook register plugins
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("say_hello", say_hello);
}

pub fn say_hello(ecx: &mut ExtCtxt, sp: codemap::Span, args: &[tokenstream::TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = ecx.new_parser_from_tts(args);

    let tt = parser.parse_token_tree();
    if let tokenstream::TokenTree::Token(_, Token::Literal(Lit::Str_(type_name), _)) = tt {
        let type_name_ident = Ident::from_str(format!("{}", type_name).as_str());

        let output = quote_expr!(ecx, {
            <$type_name_ident>::hello_world()
        });
        MacEager::expr(output)
    } else {
        ecx.parse_sess().span_diagnostic.err(format!("failure parsing type name from {:?}", &tt).as_str());
        DummyResult::any(sp)
    }
}

output of cargo test --test say_hello -- --nocapture

running 4 tests
test say_hello_basics ... 
Result from plugin = Hello world !!!
ok
test say_hello_v1 ... ok
test say_hello_v2 ... ok
test say_hello_v3 ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out