Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macro_rules that "captures" outter variable fails to compile if used inside a function, but not if used inside a method #15504

Closed
japaric opened this issue Jul 7, 2014 · 5 comments
Labels
A-syntaxext Area: Syntax extensions

Comments

@japaric
Copy link
Member

japaric commented Jul 7, 2014

STR:

#![feature(macro_rules)]

macro_rules! check_len {
    () => { xs.len() == 0 }
}

struct Foo;

impl Foo {
    fn bar(self, xs: Vec<uint>) -> bool {
        // GOOD
        check_len!()

        // GOOD
        //xs.len() == 0
    }
}

fn baz(xs: Vec<uint>) -> bool {
    // BAD
    check_len!()

    // GOOD
    //xs.len() == 0
}

fn main() { }

Output:

$ rustc foo.rs
foo.rs:4:13: 4:15 error: unresolved name `xs`.
foo.rs:4     () => { xs.len() == 0 }
                     ^~
foo.rs:3:1: 5:2 note: in expansion of check_len!
foo.rs:21:5: 25:2 note: expansion site
error: aborting due to previous error

Expansion:

$ rustc --pretty expanded foo.rs
#![feature(macro_rules)]
#![feature(phase)]
#![no_std]
#![feature(globs)]
#[phase(plugin, link)]
extern crate std;
extern crate native;
use std::prelude::*;


struct Foo;

impl Foo {
    fn bar(self, xs: Vec<uint>) -> bool {

        // GOOD
        xs.len() == 0

        // GOOD
        //xs.len() == 0
    }
}

fn baz(xs: Vec<uint>) -> bool {

    // BAD
    xs.len() == 0

    // GOOD
    //xs.len() == 0
}

fn main() { }

Version:

$ rustc --version
rustc 0.11.0 (4f120e6bafe971452adfede158a7957b00562a4e 2014-07-07 08:16:29 +0000)

P.S. I got the impression that we are moving towards macros that can't "capture" outter variables (like xs in this case) for "hygienic" reasons. Is that correct? And, if that's the case, may I ask what is this "hygiene" about? (Is a concept related to compilers (only)?)

@brson
Copy link
Contributor

brson commented Jul 7, 2014

cc @jbclements

@jbclements
Copy link
Contributor

Okay, let's see.

First things first: you're right that methods are currently different from functions; that's because hygiene
isn't yet complete for methods. I hope to finish that later today :).

Next, Hygiene. The basic idea is that in general, macros such as your check_len are fragile; specifically, a programmer who comes to this code later and edits the baz function might change the name of the xs argument, and all uses of xs inside that function. That programmer will probably be unhappy and confused when the code stops working. The basic idea behind hygiene is that bindings have lexical scope, even in the presence of macros.

In this instance, the easiest fix would simply be to put the definition of the check_len macro inside of the function itself. Alternatively, you could have the macro accept the name xs explicitly. I'd be happy to show you some example code, if you like.

Thanks for your question!

@japaric
Copy link
Member Author

japaric commented Jul 7, 2014

Next, Hygiene. The basic idea is that in general, macros such as your check_len are fragile; specifically, a programmer who comes to this code later and edits the baz function might change the name of the xs argument, and all uses of xs inside that function. That programmer will probably be unhappy and confused when the code stops working.

That makes sense.

The basic idea behind hygiene is that bindings have lexical scope, even in the presence of macros.

With these hygiene rules, would it be possible for the compiler to catch these kind of errors in the macro definiton? Because, right now, all errors point to the macro expansions. If you used a bad macro ten times, you'll get ten errors - it'll be better (less noisy) if the compiler pointed directly at the culprit: the macro definition.

the easiest fix would simply be to put the definition of the check_len macro inside of the function itself

In that case, I would end with two equal macro definitions: one in bar() and another in baz(). Right?

Alternatively, you could have the macro accept the name xs explicitly.

Something like this?

macro_rules! check_len {
    ($collection:ident) => { $collection.len() == 0 }
}

@jbclements Thanks for your answer!

(Should I leave this issue open until the method hygiene patch lands, or just close it?)

@jbclements
Copy link
Contributor

Re: catching the errors in the definition: the problem here is that there are things that macros don't know about their contexts. There are certain macros that can be used as patterns or as expressions or as statements; the compiler doesn't know how it'll be used until it plugs it in. Another way of saying that: this is still an area of active research!

You can go ahead and close this now, if you like. Naturally, you can also wait, if you prefer that.

@japaric
Copy link
Member Author

japaric commented Jul 10, 2014

@jbclements 👍
Fixed in #15537. Closing.

@japaric japaric closed this as completed Jul 10, 2014
bors added a commit to rust-lang-ci/rust that referenced this issue Sep 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-syntaxext Area: Syntax extensions
Projects
None yet
Development

No branches or pull requests

4 participants