On Wed, Jun 17, 2026, at 4:32 PM, Rob Landers wrote:
> Hello internals,
>
> I was reminded of my records RFC today, and one of the features of the 
> RFC was "short constructors" generally called "primary constructors" in 
> C#/Kotlin.
>
> They would look like this:
>
> class Point(public int $x, int $id = 0) extends Base($id);
>
> Which is just sugar for this:
>
> class Point extends Base {
>   public function __construct(public int $x, int $id = 0) {
>     parent::__construct($id);
>   }
> }
>
> or this:
>
> class Point extends Base {
>   public int $x;
>   public function __construct(int $x, int $id = 0) {
>     $this->x = $x;
>     parent::__construct($id);
>   }
> }
>
> A class with a primary constructor *may not* have a defined 
> `__construct` function. Any special initialization must be done with 
> hooks:
>
> class Temperature(
>   public float $celsius {
>    set {
>      if ($value < -273.15) {
>        throw new ValueError('below absolute zero');
>      }
>      $this->celsius = $value;
>    }
>  }
> ) {}
>
> new Temperature(20.0);   // ok
> new Temperature(-300.0); // ValueError: below absolute zero
>
> I'm sending this email to the list to gather additional feedback before 
> pursuing a formal RFC proposal.
>
> — Rob

I would support this, and I agree that KISS is the best way to go for now.

I don't know if it makes sense to include in the initial design, but for 
reference here's how Kotlin handles construction:

class Person(val name: String, val age: Int) {  // Primary constructor, 
equivalent to promotion and what's proposed here.
  init {
    // Argument-less code block/method that runs after the primary constructor 
is assigned.
    // This could be useful to include for more complex cases, like where hooks 
are useful.
  }

  // Secondary constructor
  constructor(name: String): this(name, 0) {
    // This constructor gets called first, but is forced to call the primary 
constructor syntactically.
    // Then it can do other stuff if it wants.
  }
}

cf: https://kotlinlang.org/docs/classes.html#constructors-and-initializer-blocks

I don't think we should include the "secondary constructor calls primary 
constructor and you have to make sure it works" logic.  However, I do think we 
can/should consider an init block.  Though how that interacts with inheritance 
I'm not sure yet.

Putting non-trivial property hooks inside a constructor promotion is already 
strongly discouraged, and I would recommend continuing to discourage them even 
with this syntax.  However, that does raise a concern that we would really 
benefit from some way to allow property hooks to be defined "on their own" on a 
property, AND link the property to a promoted constructor argument.  I'm not 
sure off hand how we would do that, and it's somewhat tangential to this 
syntax, but I am pointing it out for completeness.

As for the readonly/hooks incompatibility, honestly, I don't care. :-)  
Readonly is frequently problematic, Internals has already rejected making 
readonly and hooks compatible, and `protected(set)` gets you almost the same 
rules with none of the conflicts.  So I'm good there.

--Larry Garfield

Reply via email to