@property fields

Jonathan M Davis newsgroup.d at jmdavisprog.com
Thu Dec 5 23:00:50 UTC 2024


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.

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 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.

> 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.

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





More information about the dip.ideas mailing list