I'd like to propose to add properties to the language. When invoked, they look like regular fields. They're similar to Kotlin properties and Javascript getters/setters.
They come in two flavors:
1. Automatic implementation, a.k.a. delegation
pub struct Point {
x: f32,
y: f32,
}
pub struct Square {
left_top: Point,
size: f32,
}
pub trait XY {
x: f32;
y: f32;
}
impl XY for Point {
x;
y;
}
impl XY for Square {
x = self.left_top.x;
y = self.left_top.y;
}
The expressions that are assigned to x
and y
must be allowed on the left-hand side of an assignment. They may include fields, properties and indexing expressions.
If this form of delegation is added to the language, it would make sense to also allow delegating functions. But that's not my main priority.
2. Manual implementation
pub trait Diag {
diagonal {
get(&self) -> f32;
set(&mut self, new: f32);
}
}
impl Diag for Square {
diagonal {
get(&self) -> f32 {
self.size * std::f32::consts::LN_2
}
set(&mut self, new: f32) {
self.size = new / std::f32::consts::LN_2;
}
}
}
get
and set
are context-sensitive keywords. Their functions are invoked implicitly when accessing or assigning to a property. You don't have to specify both; a property can be read-only or write-only.
Properties work for owned values as well as borrowed values. For example, we can write
pub trait Foo {
bar {
get(self) -> Bar;
get(&self) -> &Bar;
}
}
When bar
is accessed, the first getter is preferred. The second one is only used if the first isn't applicable:
fn test(foo: impl Foo) {
let _: Bar = foo.bar;
let _: &Bar = (&foo).bar;
}
A property can have a getter accepting &self
or &mut self
, but not both. If you need both, use two properties, e.g. bar
and bar_mut
.
Properties without a trait
Properties can appear in an impl
block:
impl Point {
inv {
pub get(&self) -> Point {
Point { x: -self.x, y: -self.y }
}
}
}
Although invoking a property looks like a field, properties are very much like methods. For example, there can be a property with the same name as a field.
In this case, the field has precedence over the property. To invoke the property, you need the fully-qualified syntax, e.g. XY::inv::get(point)
.
Usage
When a getter is present and visible, a property can be accessed like a field with the dot syntax. When a setter is present and visible, it can be assigned a value. The assignment operators +=, *=, %=, &=, >>=
, etc. require both a getter and a setter.
Unresolved questions
How to get a getter or setter of a type or trait? E.g.
point.x === XY::x::get(point)
foo(|p| p.x) === foo(XY::x::get)
These are ambiguous if there's more than one getter, but I don't have a better idea ATM.
Maybe it's better to allow at most 1 getter and 1 setter?
Using properties should feel like using a field. Ideally it should be possible to replace public fields with properties in a backwards-compatible way. However, I'm afraid that's not possible because of how fields of borrowed values behave. Also, properties can not be used when pattern-matching or destructuring.
I'm not entirely satisfied with the syntax.
A different syntax for delegation was proposed a few weeks ago, I'm not sure which is better:
impl XY for Point {
x = self.left_top.x;
y = self.left_top.y;
}
impl XY for Point {
use self.left_top::{x, y};
}
I'm totally open for suggestions!