Thread overview
Constructor Parameter vs Member Variable
Dec 05
DrDread
Dec 05
DrDread
December 05

If you have a struct member variable named a and a constructor for that struct that takes a parameter named a, it isn't obvious what the usage of a in the body refers to.

import std;

struct F
{
    int a;
    int b;

    this(double a)
    {
        writeln(a);
        a = a;
    }
}

void main()
{
    scope f = F(15);

    writeln(f.a);
}

The behavior is extremely unintutive, and arguably broken, in that it a) compiles and b) will print 15 on the first line and 0 on the second line. Additionally, if you rename the constructor parameter to be c, then it fails to compile (because you can't assign a double to an int).

Is there a way to have the compiler warn or error in this situation? Or in general does anyone know ways to avoid the ambiguity, other than just being careful with names?

December 04
On Monday, December 4, 2023 7:41:51 PM MST Christopher Winter via Digitalmars- d wrote:
> If you have a struct member variable named `a` and a constructor for that struct that takes a parameter named `a`, it isn't obvious what the usage of `a` in the body refers to.
>
> ```
> import std;
>
> struct F
> {
>      int a;
>      int b;
>
>      this(double a)
>      {
>          writeln(a);
>          a = a;
>      }
> }
>
> void main()
> {
>      scope f = F(15);
>
>      writeln(f.a);
> }
> ```
>
> The behavior is extremely unintutive, and arguably broken, in that it a) compiles and b) will print 15 on the first line and 0 on the second line. Additionally, if you rename the constructor parameter to be `c`, then it fails to compile (because you can't assign a double to an int).
>
> Is there a way to have the compiler warn or error in this situation? Or in general does anyone know ways to avoid the ambiguity, other than just being careful with names?

This behavior is actually extremely common in OO programming languages. The solution to this is to use the this reference when there's any ambiguity. The more local symbol is always going to win when one symbol shadows another, and while some kinds of symbol shadowing are disallowed in D, completely disallowing it can be annoying (e.g. plenty of programmers would be annoyed if they had to name constructor parameters differently from member variables when the whole point of the parameter is to assign to the member variable). So, in your example, you'd do

this(double a)
{
    this.a = cast(int) a;
}

Of course, it's complicated by the fact that you made the parameter double, and the member variable int, whereas usually, the types would match (so the cast wouldn't be required), but in general, it's extremely common (both in D and other OO languages such as C++, Java, etc.) to name parameters to a constructor with the exact same name as the member variables that they're intended to be assigned to.

Now, the other solution to this is to name member variables differently from other variables, and when they're private, this is extremely common practice, in which case, just looking at the variable name makes it's clear that you're looking at a private member variable. The most common naming schemes for such member variables would be to prepend them with an underscore or to prepend them with m_. e.g.

struct S
{
    private int _a;

    this(int a)
    {
        _a = a;
    }
}

But that sort of thing isn't normally done with public members.

- Jonathan M Davis



December 05

On Tuesday, 5 December 2023 at 04:31:28 UTC, Jonathan M Davis wrote:

>

This behavior is actually extremely common in OO programming languages.

Yeah, I understand that it is variable shadowing, but this nearly identical situation is rejected by the compiler:

void fun(double a)
{
    int a = cast(int) a; <-- Error: variable `a` is shadowing variable `main.fun.a`
}

I don't really understand why these scenarios would be treated differently by the compiler (I would even argue that my intention here is more explicit vs in the constructor, but not everyone may agree with that).

>

The solution to this is to use the this reference when there's any ambiguity.

Yeah, I suppose for me always using this is probably the best way to avoid any ambiguity.

December 05

On Tuesday, 5 December 2023 at 14:23:26 UTC, Christopher Winter wrote:

>

On Tuesday, 5 December 2023 at 04:31:28 UTC, Jonathan M Davis wrote:

>

This behavior is actually extremely common in OO programming languages.

Yeah, I understand that it is variable shadowing, but this nearly identical situation is rejected by the compiler:

void fun(double a)
{
    int a = cast(int) a; <-- Error: variable `a` is shadowing variable `main.fun.a`
}

I don't really understand why these scenarios would be treated differently by the compiler (I would even argue that my intention here is more explicit vs in the constructor, but not everyone may agree with that).

>

The solution to this is to use the this reference when there's any ambiguity.

Yeah, I suppose for me always using this is probably the best way to avoid any ambiguity.

you can't really prevent parameter names to be the same as member var names, or else you'll break metaprogramming. so sadly there is no way to solve this ambiguity except writing this. if you want to access a member.

December 05
On Tuesday, December 5, 2023 7:23:26 AM MST Christopher Winter via Digitalmars-d wrote:
> On Tuesday, 5 December 2023 at 04:31:28 UTC, Jonathan M Davis
>
> wrote:
> > This behavior is actually extremely common in OO programming languages.
>
> Yeah, I understand that it is variable shadowing, but this nearly identical situation is rejected by the compiler:
>
> ```
> void fun(double a)
> {
>      int a = cast(int) a; <-- Error: variable `a` is shadowing
> variable `main.fun.a`
> }
> ```
>
> I don't really understand why these scenarios would be treated differently by the compiler (I would even argue that my intention here is more explicit vs in the constructor, but not everyone may agree with that).

A big difference is that if you shadow a local variable with another local variable, you don't have any way to differentiate between them. So, when you shadow a local variable, that variable becomes inaccessible within that section of code, whereas with a member variable, you can distinguish with the this reference, and with a variable at module scope, you can differentiate a leading dot. So, the error prevents you from making variables inaccessible, whereas there is no need for such an error when shadowing non-local variables.

Also, shadowing non-local variables is sometimes hard to avoid, whereas with a local variable, you're in control of all of the names within the function, and it's pretty rare that there would even arguably be a good reason for shadowing a local variable. If a programmer does it, it's almost certainly a mistake, and they're going to want the compiler to tell them about it.

On the other hand, a ton of programmers very much want to shadow member variables in constructors. They want the parameter names to match the names of the variables for documentation purposes, and they're used to dealing with that shadowing, so they don't consider it a big deal. It's rarely a problem in practice, and it's the kind of bug that's found very quickly in testing if you make a mistake.

- Jonathan M Davis



December 05
On Tuesday, 5 December 2023 at 16:30:42 UTC, Jonathan M Davis wrote:
> On Tuesday, December 5, 2023 7:23:26 AM MST Christopher Winter via Digitalmars-d wrote:
>> [...]
>
> A big difference is that if you shadow a local variable with another local variable, you don't have any way to differentiate between them. So, when you shadow a local variable, that variable becomes inaccessible within that section of code, whereas with a member variable, you can distinguish with the this reference, and with a variable at module scope, you can differentiate a leading dot. So, the error prevents you from making variables inaccessible, whereas there is no need for such an error when shadowing non-local variables.
>
> [...]

a better option would be to force the use of this. to access member variables instead of implicitly making them part of the scope. but that ship has sailed a long time ago
December 05
On Tuesday, December 5, 2023 10:45:27 AM MST DrDread via Digitalmars-d wrote:
> On Tuesday, 5 December 2023 at 16:30:42 UTC, Jonathan M Davis
>
> wrote:
> > On Tuesday, December 5, 2023 7:23:26 AM MST Christopher Winter
> >
> > via Digitalmars-d wrote:
> >> [...]
> >
> > A big difference is that if you shadow a local variable with another local variable, you don't have any way to differentiate between them. So, when you shadow a local variable, that variable becomes inaccessible within that section of code, whereas with a member variable, you can distinguish with the this reference, and with a variable at module scope, you can differentiate a leading dot. So, the error prevents you from making variables inaccessible, whereas there is no need for such an error when shadowing non-local variables.
> >
> > [...]
>
> a better option would be to force the use of this. to access member variables instead of implicitly making them part of the scope. but that ship has sailed a long time ago

There are indeed some languages that do that, but a lot of us would find such a requirement to be extremely annoying, especially since in practice, problems with shadowing member variables are quite rare.

- Jonathan M Davis