@property fields

Richard (Rikki) Andrew Cattermole richard at cattermole.co.nz
Fri Dec 6 01:48:44 UTC 2024


On 06/12/2024 12:00 PM, Jonathan M Davis wrote:
> On Tuesday, December 3, 2024 4:50:20 PM MST Richard Andrew Cattermole (Rikki)
> via dip.ideas wrote:
>> The ``@property`` attribute has been left in the lurch for quite
>> some time, where only one behaviour related to its return value
>> being of any value.
>>
>> There are two parts of this proposal, however I am only confident
>> in the first part, fields.
>> The other is for methods which would give you full control over
>> it.
>>
>> Syntax is quite simple:
>>
>> ``@property int field;``
>>
>> It is valid in interfaces, structs, and classes.
>>
>> ```d
>> interface IField {
>>       @property int field;
>> }
> 
>> class Thing : IField {
>>       @property int field;
>> }
>>
>> struct Thing2 {
>>       @property int field;
>> }
>> ```
>>
>> It results in three things.
>>
>> 1. A field that is either ``protected`` (classes) or ``private``
>> (structs).
> 
> If we were to do it, I would argue for just always making it private, since
> that's better encapsulation, and while having protected fields certainly
> makes sense sometimes, it's not really the best default IMHO.
> 
> Also, including interfaces in the mix seems like it would ultimately create
> confusion, since when you marked a variable with @property in a class or
> struct, you'd get an actual variable, whereas with the interface, you
> wouldn't. It doesn't take much in the way of nuance like that to make it
> just clearer to write out the functions.
> 
>> 2. It will generate a method for classes and structs in the form:
>>
>>       ```d
>>       class Thing : IField {
>>           protected int field;
>>
>>           ref typeof(this.field) __property_field() /* inferred
>> attributes */ {
>>                return this.field;
>>           }
>>       }
> 
> Returning by ref kind of defeats the purpose of @property in general.
> _Sometimes_ it makes sense, but usually, if that's what you want, it makes
> more sense to just use a field and not bother with property functions at
> all. It also doesn't necessarily play nicely with inheritance, since it
> would be pretty easy to get into a situation where returning by ref made
> sense in some parts of the hierarchy but not in others, meaning that it
> couldn't return by ref in general. Certainly, returning by ref is something
> that would make more sense with a struct than a class.

This makes operators work. Which is a very good default to have.

If you want to do something else, you can define the methods manually.

> The other big problem with returning by ref is that the main reason to use
> property functions instead of just making the field public is so that you
> can swap out the implementation later without breaking the caller, whereas
> if the function returns by ref, they can take the address just like they
> could have with a public variable, which creates the same problem.

This isn't an issue here, you can swap it to a method later on and do 
something more manual.

>>      This allows an interface to require that a field exists,
>> without defining one. Since it is the stub that gets inherited
>> (which can only be implemented using an ``@property`` field.
> 
> The could just as easily declare the property functions, and it wouldn't be
> much more typing. It would also make it explicit what the exact function
> signatures were.

It could yes, but it wouldn't look like a field.

This has come up before, and gives us an answer to how do you do a field 
in an interface.

>> Semantically:
>>
>> 1. You will not have direct access to the field outside of the
>> type. All accesses and mutation will go through the method.
>> 2. It may be passed to a function parameter that is ``ref`` or
>> ``out``. This is perfectly safe so needs no protection due to it
>> implicitly being ``scope``.
>> 3. An invariant will be called following mutation or when it is
>> passed to a ``ref`` or ``out`` function
>> parameter.
>>       3.1. For interfaces if a ``@property`` field is used, an
>> invariant will be required to be in the vtable implicitly.
>> 4. In ``@safe`` code:
>>       4.1. It may not be stored in a ref variable ``ref var =
>> thing.field;``
>>       4.2. It may not have a pointer taken to it ``&thing.field``
>>
>> As long as the invariant is called after mutation, this covers
>> the use case of setter methods for properties. It cannot be used
>> to transform, but will cause errors. If you need transformation
>> wrapping the field with a struct could introduce such behaviors
>> so need not be provided at this level (although it should be
>> supported with ``@property`` methods).
> 
> There is _some_ value in wrapping a field in property functions that don't
> do anything when dealing with a public API, since then you can change the
> implementation without affecting the folks using the library, whereas
> swapping a public variable out for property functions would break such code.
> So, there probably are cases where having the compiler generate such
> boilerplate would be useful, but it's also not much more effort to type out
> the property functions. So, I don't think that the feature would pull its
> weight.

And this is why I've stated that it must introduce new semantic behavior 
that cannot be done without it. I.e. guarantee invariant is called, 
cannot escape.

> Also, there are enough nuances with regards to what exactly the function
> signatures would look like and whether a variable would actually be created
> or not, that it feels to me like it's just doing too much which isn't
> immediately obvious. It can certainly be understood with some effort, but
> particularly if you're overriding these functions, you have to worry about
> exactly what's being generated, and ultimately, it's just going to be easier
> if you can see the actual function signatures and copy them.
> 
> I just don't think that the little bit of boilerplate that this would
> provide is worth the cost, though I'm sure that others would disagree, since
> it's not the first time that a feature along these lines has been proposed.
> 
> - Jonathan M Davis

Just to be clear, as far as I'm concerned, either this gets fully 
designed and implemented or it should be removed.

We can't keep things like this in the language where its not doing 
anything useful for people (yes I know it does one thing that could be 
replaced with a trait).

So if you have a proposal on how to make it worth keeping, please 
propose it. Otherwise I'm going to have to assume such push back is in 
favor of removal.



More information about the dip.ideas mailing list