Allow shadowing self in methods

Most (all?) of the places where you define local variables, they are actually using patterns, although function arguments and plain let statements have to be irrefutable patterns. So if you can write let self, one might also expect to allow let (self, other) or let Struct { foo: self, bar: other }, etc. Then we may also want refutable self patterns in let-else, if let, while let, and match.

If I am not wrong, self is currently not allowed in any of these contexts, which I would say should stay the same and following that, it should only be possible to rebind/shadow self where it already exists.

I am interested to know the answer to @withoutboats's question too. Is there a historical reason for this not being allowed?

It is allowed in function arguments, of course, but in limited capacity as a simple method receiver. Your post is asking for let self which is typically another pattern position, but we could limit that. There's usually nothing special about shadowing though, so that would be a new kind of restriction if let self must shadow an original method receiver.

Without commenting on shadowing, I think allowing self as a non-receiver argument would be terribly confusing; for example, the following becomes valid (an associated function, I presume):

impl Foo {
    fn foo(bar: u32, self: &Self) {}
}
3 Likes

I completely agree. self should (at least for now) remain restricted to receiver arguments.

2 Likes

General patterns would let you avoid some redundancy, e.g. if I can write this:

impl Foo {
    fn foo(&self) {
        if let Some(this) = self.inner() {
            let self = this; // new shadowing here
            todo!();
        }
    }
}

Then wouldn't it be nicer to allow if let Some(self) = self.inner()?

2 Likes

I think this is also fine since self is in scope.

Definitely. But perhaps all of this boils down to allowing self anywhere, and just adding warnings for anything that seems especially egregious. It's possible that introducing a self binding where it isn't a shadow/receiver is the only warning we might want, even then I don't see anything wrong with that.

imho that isn't a problem, since all other variables can be similarly shadowed and that isn't any more of a problem there, so why would it suddenly be a bigger problem when the variable's name happens to be self?

3 Likes

I think what I would prefer is allowing self to be used as an identifier in any binding except function arguments (other than where it is allowed now), since it has special meaning in that position. Within a function body there is no special meaning ascribed to a binding called self. That could allow for some confusing code—for example if the binding name self is used in a free-function—but I don't think that is confusing enough to worry about adding in additional rules, it can be handled by a convention of only using the self identifier for things that are in some way related to a self argument.

7 Likes

To see why, we need to:

  1. Consider what methods are
  2. Take a step back to see what it is this proposal would do.

I'll address each point in turn.

  1. Methods are functions that have additional syntactic sugar in order to accommodate the receiver within the definition, as well as accommodate the .method() caller syntax.

  2. Within the context of point 1., this proposal wishes to ignore the purpose of method syntax relative to fn syntax.

Only now does it become clear why I consider the proposal ill-conceived: this proposal wishes to accumulate the ability to undo the very raison d'etre for the method syntax.

Why not just use a fn if you don't want to use the method for its intended purpose?

1 Like

(NOT A CONTRIBUTION)

Without having actually investigated the history, it doesn't seem like there was a particular ban on shadowing self, but rather it falls out of the implications of not wanting people to use self as a "normal" variable name for things that aren't method receivers. This is specifically a problem in arguments, but it seems like bad style to me to name a variable self that isn't somehow "the same" as the method receiver in a method.

I think these are the range of options:

  • Status quo
  • Allow using self as the pattern for let bindings in functions with a self argument
  • Allow using self in all bindings in functions with a self argument
  • Allow using self as the pattern for let bindings anywhere
  • Allow using self in all bindings inside the body of any function
  • Allow using self anywhere, including non-receiver function arguments

Personally, I think I would draw the line after the 3rd or 5th item, unsure which.

4 Likes

My personal immediate suggestion would be to put the cutoff after the 5th item — allow the use of self as a pattern binding anywhere within the body of a function, but not in function arguments or as an item name — but with two additional nonstandard_style type lints:

  • Binding of self which does not shadow an existing self binding.
  • Shadowing of self which changes the receiver type.

The former should IMHO be warn-by-default, and the latter probably warn-by-default under clippy but not rustc.

The two lints together ensure that wherever a self binding is used, it's as a receiver for Self.

1 Like

I've only rarely wanted to shadow self, but often wanted to bind it in a constructor.

impl Foo {
    pub fn new(b: Bar) -> Self {
        let mut self = Self {
            b,
            c: Default::default(),
        };
        self.sync_c_with_b();
        self
    }
}

If this were to be permitted, I'd like an accompanying lint that warns by default when self is bound to a type which does not contain (does not dereference to?) Self (that is, it can be Foo, Self, Arc<Self> or other such), to discourage use of it in ways that are unrelated to methods.

15 Likes

I think the range of options is at least two dimensional: where you can you bind self, and what type self can take on. I think I'd see the ranges of the latter as

  1. Same as receiver
  2. Just Self of this impl (where there is no receiver)
  3. Some restricted set of valid receiver types (in the context of this impl) related to receiver type (or lack of receiver type)
  4. Any valid receiver type (in the context of this impl)
  5. Any type at all

And I think I come out after (4), which implies I come out on "anywhere Self is a defined alias" for the binding locations (somewhere after (3) but not (4) in that list).

I.e. self should only be allowed where there's a Self and it should be related to Self.

Maybe the binding has to annotate what type self is becoming if it changes (or lint this).

2 Likes

imho that only applies to the function's signature, not to the function's body. imho in the function body self should be treated like any other variable (currently afaik it is just like any other variable except for the restrictions on naming any new variables self), though of course it would be good style to only create self variables for thing's that in some sense are the same as the function's self argument.

Imho the current restrictions on using self in the function's signature should be left as-is, relaxing the restrictions inside the function's body is imho a good idea.

2 Likes

I agree with this: I think if we're going to allow shadowing of self, it should have to have a type that still contains Self.

Also, while I'm tempted to say that we shouldn't allow binding to self in a function without a self argument, I can see a case for it in one other scenario: constructors.

impl SomeStruct {
    fn new() -> Self {
        let self = Self { ... };
        self.method();
        self
    }
}

So, I think where I'd land right now is "you may shadow it in a method that takes self, or bind it in a method that returns a type containing Self; in either case you may only bind it to a type containing Self".

(How much of that should be an error and how much a lint I don't know.)

11 Likes

Limiting it to a type that contains Self would not support pin-project, when using that you commonly want to let self = self.project(); which changes the type to an unrelated one with the same set of field names containing references to the original fields.

8 Likes

Limiting it to a type that contains Self would not support pin-project, when using that you commonly want to let self = self.project(); which changes the type to an unrelated one with the same set of field names containing references to the original fields.

True, although I have always seen pin-project as a temporary solution similar to async-trait and like async-trait, I would expect pin-project to eventually be superseded by support from a combination of the core language and the standard library. For this reason, I still wouldn't rule out limiting to types that contain Self since pin-project, in my opinion, is an outlier/temporary workaround.

2 Likes

I just remembered another place in which self is special and definitely rules out using it as an item identifier: use foo::self; and use self::foo;.

3 Likes