I don’t think it’s a stretch to say that a proposal like this would meet a great deal of opposition, so you will need to be prepared to answer to a lot of counterarguments.
Here are my own gut personal feelings:
Properties make it difficult to reason about code
Believe me when I say that there are some people who dearly love the status quo here; I am one of these people.
When reading code, I personally take a great deal of comfort in knowing that a.x = 3;
does not run arbitrary code. Granted, this is a fairly universal argument that can be applied against a lot of features; but this particular feature has burned me many many times in Python, where I have now learned to live in constant paranoia of anything that could be a property.
>>> from pymatgen.core.structure import Structure
>>> structure = Structure(np.eye(3), ["C"], np.zeros((1,3)))
>>>
>>> # This looks like a field
>>> structure.frac_coords
array([[0, 0, 0]])
>>>
>>> # But if you try modifying it...
>>> structure.frac_coords[0] = [0, 2, 2]
>>> structure.frac_coords
array([[0, 0, 0]])
Granted, my view of the feature is colored by my experience of it in Python. A new language means a new terrain. It could be possible that most of my reasons for objecting to it no longer apply here… or it’s possible that they still do. I’d have to reflect more on what precisely these reasons are.
Notably, rust does have overloading for the other lvalue-producing forms (Deref
, DerefMut
, Index
, IndexMut
), and I’ve never been too strongly bothered by this because implementations are harshly limited by the requirement to produce &T
or &mut T
. Perhaps with that same limitation in place here, I might be more open to the feature.
Of course, with such a limitation in place, they’re hardly powerful enough to replace setters. (To which I argue: that’s the point.)
The motivation
The ergonomics argument
This is the motivation you mentioned, and I simply do not buy it. Most of the time when I write setters, it is on some kind of builder-pattern type, which already receives ergonomical benefits over setters thanks to method-chaining.
Properties would not obviate the need for the builders, as one of the greatest advantages of builders are to defer validation of the consistency between arguments until all arguments have been supplied. (How frustrating is it when e.g. x.set_visible(true).set_enabled(true)
succeeds while x.set_enabled(true).set_visible(true)
throws an exception due to an invalid intermediate state?)
Maybe there are other use-cases not served by builders. I don’t know. But it needs a compelling example.
The stability argument
A common argument in support of properties is that library authors should be able to replace a field with getters and setters without impacting public API. I am not generally moved by this motivation, however, as I feel that changes like this are simply uncommon in a language like Rust that tends to encourage the creation of many simple datatypes with a small number of fields.
Basically, when you make a field public (or when you write a trivial setter), you’re making a strong statement that the datatype is valid for all possible states of that field. I find it hard to believe that such a strong statement may become false in the future.
There has been one example I am aware of of a field that was changed to getters and setters in a widely-used API. That was when the span
fields in syn 0.12 were replaced with span
and set_span
methods.
I don’t know the full reason behind this; it appears that similar changes were made to proc_macro
API for proc macros 1.2. All implementations of the method that I can find appear to be trivial setters, except for one defined on an enum type that simply calls the method on each variant. I do not know whether there is any reason to believe that nontrivial behavior might be added to these setters, or if this change was simply made out of extreme caution.
It’s worth noting that, at least for the current behavior of the setters, my proposal of limiting the implementations to return &T
/&mut T
would suffice.