Introducing `readonly`
As of HHVM version 4.139, we’ve released a new keyword called readonly
in Hack. readonly
is a new feature in Hack for restricting the mutability of object properties. It’s designed for use cases that require performant, runtime-enforced mutability control.
readonly
has two main principles: readonlyness and deepness.
- Readonlyness: Object properties of any readonly value cannot be modified (i.e. mutated).
- Deepness: All nested properties of a readonly value are readonly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Bar {
public function __construct(
public Foo $foo,
){}
}
class Foo {
public function __construct(
public int $prop,
) {}
}
function test(readonly Foo $foo, readonly Bar $bar) : void {
$foo->prop = 5; // error, $foo is readonly
// Deepness: $x is also readonly
$x = $bar->foo;
$x->prop = 3; // error, $x is readonly
}
The readonly keyword can be used in many places in Hack.
- Readonly expressions: Any expression can be marked readonly with the
readonly
keyword. - Readonly parameters: A parameter marked readonly signal that a function or method can take in a readonly value, and does not modify it.
- Readonly return types: A function can return readonly, which allows it to return a readonly reference.
- Readonly properties: A property can be marked readonly, which allows you to assign a readonly value to it. Note that readonly properties can have their values replaced, as readonly is a modifier on the value of the property, not the property definition itself.
- Readonly methods: An instance method can be marked readonly. Instance methods that are readonly treat
$this
as readonly. Readonly values can only call readonly methods. - Readonly closures: A readonly closure captures external variables as readonly.
You can see some examples of the readonly keyword in this code snippet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Bar {
public int $prop = 0;
}
class Foo {
public function __construct(public Bar $bar, public readonly Bar $ro_bar) {}
// This method promises not to modify $this
public readonly function getBar(): readonly Bar {
// note that $this is readonly, so $this->prop is readonly, which is why
// this method must return readonly
return $this->bar;
}
public static function takesReadonlyExample(readonly Foo $x): void {
$x->bar = new Bar(); // error, $x is readonly here!
}
public static function readonlyClosureExample(): void {
$x = new Bar();
$readonly_f = readonly (): void ==> {
$x->prop = 5; // error, $x is captured as readonly since $readonly_f is a readonly function
};
$x->prop = 4; // $x is still mutable out here, so this is okay!
}
public function readonlyPropExample(Foo $foo): void {
$x = readonly new Bar();
$foo->bar = $x; // error, $x is readonly but bar is a regular property
$foo->ro_bar = $x; // ok!
}
}
Some function calls and property accesses require explicitly annotating the result of the call as readonly:
- If you call a function that returns a readonly value, you must annotate the call with the
readonly
keyword. - If you access a class’s readonly property, you must annotate the access with the
readonly
keyword.
1
2
3
4
5
6
function testExplicitReadonly(readonly Foo $foo): void {
// vvvvvvvv This keyword is required here since getBar returns readonly
$bar = readonly $foo->getBar();
// vvvvvvvv This keyword is required here since $ro_bar is a readonly property
$ro_bar_prop = readonly $foo->ro_bar;
}
For a full guide and specification to using readonly, you can check our full documentation on the feature.