Skip to content

Allow for self to be used inside blocks passed to macros #1606

@tinco

Description

@tinco

As reported in rust-lang/rust#15682 it is currently not allowed use the word self in a block passed to a macro because of hygiene issues.

struct A {
    pub v: i64
}

macro_rules! pretty {
    ( $t:ty => $body:block ) => (
        impl $t {
            pub fn pretty(&self) -> String $body
        }
    )
}

pretty! (A => {
    format!("<A:{}>", self.v)
});

fn main() {
    println!("Pretty: {}", A { v: 3 }.pretty());
}

Proposal

I would like to propose to implement the workaround suggested by @Ryman and make an exception for the self keyword in the macro hygiene checker. Afterwards an issue/rfc can be made for a more complete solution.

Advantage

It will enable a class of macros that are currently unavailable. There are some workarounds but they make the macros less intuitive to use, for example the previous example could be written like this:

macro_rules! pretty {
    ( $t:ty, $_self:ident => $body:block ) => (
        impl $t {
            pub fn pretty(&$_self) -> String $body
        }
    )
}

And used like this:

pretty! (A, self => {
    format!("<A:{}>", self.v)
});

This works but is unintuitive and gives a confusing error when used with another ident than self.

Disadvantage

@huonw pointed out that this solution will break when an implementation is nested inside a method. This is valid of course, but to me it feels like a bit of a convoluted case. If it does happen in the real world I feel it would not likely fail silently, as both selfs would have to share an interface for the compilation to succeed.

Should this feature be blocked because it has no perfect solution right now? (A more complete suggestion was suggested by @Ryman but it looks to me like it would be a deep and complex hack)

Activity

durka

durka commented on May 3, 2016

@durka
Contributor

This is a papercut that we should fix. +1.

petrochenkov

petrochenkov commented on May 4, 2016

@petrochenkov
Contributor

because of hygiene issues.

It's not hygiene "issues", it's hygiene. In general, I don't like the idea of replacing the correct hygiene algorithm with some hacky hacks.

I'll try to poke around in resolve if I have time, but I expect issues. As a minimum, if self becomes unhygienic, it'll break code passing self or self.something to macros. You pass one self to a macro and it turns into another self inside the macro.

durka

durka commented on May 4, 2016

@durka
Contributor

@petrochenkov another solution could be to allow other names for the self parameter in methods (maybe only when generated by macros?). The half-keyword status of self right now causes the following conundrum when you want to define a macro like @tinco's:

  1. You must pass an ident to the macro to use as self, because hygiene otherwise prevents the two selfs from being the same.
  2. You can't pass any other ident than self, because the parser (unhygienically) checks that method receivers are named self.

Besides not being discoverable from error messages, these two requirements combined make it seem like useless "Simon says" boilerplate.

tinco

tinco commented on May 6, 2016

@tinco
Author

After working on my macros some more I think I agree with @petrochenkov now. self is just like any other method parameter in that it should be explicitly introduced by the macro so that it is hygienically captured.

I think if Rust wants to have Ruby-like powerful DSL macro's there would have to be an 'unclean' directive and things like ident manipulation and stuff like that. I'm not familiar enough with Rust's philosophy to know whether that's desirable, but I'm thinking probably not.

@durka's point 2 still stands though, but I don't see how that could be solved unless Rust would simply allow any function that takes the right type as the first argument to be callable as a method. I bet that's already been discussed somewhere a long time ago when impl was designed and it's no doubt outside the scope of this issue.

arielb1

arielb1 commented on May 7, 2016

@arielb1
Contributor

@petrochenkov

I'll try to poke around in resolve if I have time, but I expect issues. As a minimum, if self becomes unhygienic, it'll break code passing self or self.something to macros. You pass one self to a macro and it turns into another self inside the macro.

What code does this break? self is only bound by methods, and you can't use self from outside of a method inside of it. Self the type is already unhygienic, and I think that the 2 should be handled in the same way:

#[derive(Default,Debug)]
struct A {
    pub v: i64
}

macro_rules! pretty {
    ( $t:ty => $body:block ) => (
        impl $t {
            pub fn pretty(&self) -> String $body
        }
    )
}

pretty! (A => {
    format!("<A:{:?}>", Self::default())
});

fn main() {
    println!("Pretty: {}", A { v: 3 }.pretty());
}
petrochenkov

petrochenkov commented on May 7, 2016

@petrochenkov
Contributor

@arielb1

self is only bound by methods, and you can't use self from outside of a method inside of it.

Yeah, you are right, sorry. Everything I expected to break doesn't work already with "can't capture dynamic environment ..." errors.

Self the type is already unhygienic, and I think that the 2 should be handled in the same way

From name resolution point of view self behaves exactly like other function parameter names and Self behaves like other type names, so it's the usual separation between hygienic local variables and unhygienic items. I expect the whole problem to go away eventually with @nrc's macros/syntax extensions 2.0 allowing unhygienic locals and hygienic items.

ticki

ticki commented on May 7, 2016

@ticki
Contributor

I feel like that this breaks hygiene. One should rather pass self to the macro, or in the glorious future, self.macro!(). On the other hand, it seems convenient.

petrochenkov

petrochenkov commented on May 7, 2016

@petrochenkov
Contributor

Ok, I've submitted rust-lang/rust#33485, let's see what happens.

joelself

joelself commented on May 24, 2016

@joelself

Aaaaand it got closed.

joelself

joelself commented on Jun 17, 2016

@joelself

It appears that self has been made unhygienic, whether on purpose or on accident. I pulled the latest changes from master and deleted my changed code because it was completely out-of-date, but left in the test. I did a configure, make rustc-stage1 and a make check-stage1 and lo and behold my unhygienic-self test passed!

test [run-pass] run-pass/self-unhygienic.rs ... ok

I tried both rust stable:

rustc 1.9.0 (e4e8b6668 2016-05-18)
cargo 0.10.0-nightly (10ddd7d 2016-04-08)

and nightly:

rustc 1.11.0-nightly (bb4a79b08 2016-06-15)
cargo 0.12.0-nightly (5a26b65 2016-06-14)

You can see the code for the test here.

Edit: I still couldn't believe it myself so I found the built rustc an compiled the test and ran it. No errors on compile or run. I added a println! to be sure:

println!("{}", A{ v: 42 }.pretty());

and sure enough:

-bash-3.2bin$ ./self-unhygienic 
<A:42>

I'm going to submit a pull request for the test to make the rust-lords decide if this is an intended feature or not.

Edit 2: Pull request submitted: #34317

jseyfried

jseyfried commented on Jun 17, 2016

@jseyfried

self is still hygienic, see this comment.

added
T-langRelevant to the language team, which will review and decide on the RFC.
on Aug 18, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-langRelevant to the language team, which will review and decide on the RFC.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @tinco@durka@nrc@joelself@arielb1

        Issue actions

          Allow for self to be used inside blocks passed to macros · Issue #1606 · rust-lang/rfcs