Michael Powell
Michael Powell

Reputation: 913

Rust: Is there a way to call a static function on a macro type argument?

This code:

#![feature(macro_rules)]

macro_rules! new(
    ($my_type:ty) => ( $my_type::new() );
)

struct Foo {
    blah: int
}

impl Foo {
    fn new() -> Foo {
        return Foo { blah: 0 }
    }
}

fn main() {
    let my_foo = new!(Foo);
    println!("Foo's value: {}", my_foo.blah);
}

Looks good enough, but it fails with this error:

test.rs:4:25: 4:32 error: unexpected token: `Foo`
test.rs:4     ($my_type:ty) => ( $my_type::new() );
                                  ^~~~~~~

If I go into the macro and replace $my_type with Foo it compiles and runs just fine, so Foo is clearly valid in that position. Unless Foo comes from macro substitution, apparently.

If I run rustc test.rs --pretty expanded, it doesn't show me the expanded macro. It just gives me the same error message. I suspect this means it's generating the message before it expands the macro, but it might just be that it doesn't show me anything unless the compile succeeds. Though that would severely limit the usefulness of --pretty expanded.

Based on other experiments, I can use the macro type arguments in basically every other place one would expect a type to work. You just can't call static functions on them. This seems like a rather arbitrary restriction, and the error message is certainly not helpful.

Why does this restriction exist? And is there a way around it?

Upvotes: 4

Views: 3061

Answers (1)

huon
huon

Reputation: 102006

The Foo::bar() syntax is creating the path Foo::bar and then calling that function, and only works with valid paths, it doesn't work with arbitrary types, e.g. (u8, i8)::bar() doesn't work. You can use the ident macro non-terminal, which takes a single identifier and can be used whereever an identifier is valid, including inside a path

#![feature(macro_rules)]

macro_rules! new(
    ($my_type: ident) => ( $my_type::new() );
)

struct Foo {
    blah: int
}

impl Foo {
    fn new() -> Foo {
        return Foo { blah: 0 }
    }
}

fn main() {
    let my_foo = new!(Foo);
    println!("Foo's value: {}", my_foo.blah);
}

UFCS offers calling such methods on arbitrary types, via the syntax <Type>::new() and so, when that is implemented, replacing your current macro with

macro_rules! new(
    ($my_type: ty) => ( <$my_type>::new() );
)

should work too.

Upvotes: 7

Related Questions