As reported in https://github.com/rust-lang/rust/issues/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());
}
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.
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.
@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)
This is a papercut that we should fix. +1.
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.
@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:
self, because hygiene otherwise prevents the two selfs from being the same.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.
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.
@petrochenkov
I'll try to poke around in resolve if I have time, but I expect issues. As a minimum, if
selfbecomes unhygienic, it'll break code passingselforself.somethingto macros. You pass oneselfto a macro and it turns into anotherselfinside 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());
}
@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.
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.
Ok, I've submitted https://github.com/rust-lang/rust/pull/33485, let's see what happens.
Aaaaand it got closed.
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
self is still hygienic, see this comment.
Most helpful comment
I feel like that this breaks hygiene. One should rather pass
selfto the macro, or in the glorious future,self.macro!(). On the other hand, it seems convenient.