diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d22ef4fe3..3db516fda4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,97 @@ ### Language +Dart 3.3 adds [extension types] to the language. To use them, set your +package's [SDK constraint][language version] lower bound to 3.3 or greater +(`sdk: '^3.3.0'`). + +#### Extension types + +[extension types]: https://github.com/dart-lang/language/issues/2727 + +An _extension type_ wraps an existing type with a different, static-only +interface. It works in a way which is in many ways similar to a class that +contains a single final instance variable holding the wrapped object, but +without the space and time overhead of an actual wrapper object. + +Extension types are introduced by _extension type declarations_. Each +such declaration declares a new named type (not just a new name for the +same type). It declares a _representation variable_ whose type is the +_representation type_. The effect of using an extension type is that the +_representation_ (that is, the value of the representation variable) has +the members declared by the extension type rather than the members declared +by its "own" type (the representation type). Example: + +```dart +extension type Meters(int value) { + String get label => '${value}m'; + Meters operator +(Meters other) => Meters(value + other.value); +} + +void main() { + var m = Meters(42); // Has type `Meters`. + var m2 = m + m; // OK, type `Meters`. + // int i = m; // Compile-time error, wrong type. + // m.isEven; // Compile-time error, no such member. + assert(identical(m, m.value)); // Succeeds. +} +``` + +The declaration `Meters` is an extension type that has representation type +`int`. It introduces an implicit constructor `Meters(int value);` and a +getter `int get value`. `m` and `m.value` is the very same object, but `m` +has type `Meters` and `m.value` has type `int`. The point is that `m` +has the members of `Meters` and `m.value` has the members of `int`. + +Extension types are entirely static, they do not exist at run time. If `o` +is the value of an expression whose static type is an extension type `E` +with representation type `R`, then `o` is just a normal object whose +run-time type is a subtype of `R`, exactly like the value of an expression +of type `R`. Also the run-time value of `E` is `R` (for example, `E == R` +is true). In short: At run time, an extension type is erased to the +corresponding representation type. + +A method call on an expression of an extension type is resolved at +compile-time, based on the static type of the receiver, similar to how +extension method calls work. There is no virtual or dynamic dispatch. This, +combined with no memory overhead, means that extension types are zero-cost +wrappers around their representation value. + +While there is thus no performance cost to using extension types, there is +a safety cost. Since extension types are erased at compile time, run-time +type tests on values that are statically typed as an extension type will +check the type of the representation object instead, and if the type check +looks like it tests for an extension type, like `is Meters`, it actually +checks for the representation type, that is, it works exactly like `is int` +at run time. Moreover, as mentioned above, if an extension type is used as +a type argument to a generic class or function, the type variable will be +bound to the representation type at run time. For example: + +```dart +void main() { + var meters = Meters(3); + + // At run time, `Meters` is just `int`. + print(meters is int); // Prints "true". + print([] is List); // Prints "true". + + // An explicit cast is allowed and succeeds as well: + List meterList = [1, 2, 3] as List; + print(meterList[1].label); // Prints "2m". +} +``` + +Extension types are useful when you are willing to sacrifice some run-time +encapsulation in order to avoid the overhead of wrapping values in +instances of wrapper classes, but still want to provide a different +interface than the wrapped object. An example of that is interop, where you +may have data that are not Dart objects to begin with (for example, raw +JavaScript objects when using JavaScript interop), and you may have large +collections of objects where it's not efficient to allocate an extra object +for each element. + +#### Other changes + - **Breaking Change** [#54056][]: The rules for private field promotion have been changed so that an abstract getter is considered promotable if there are no conflicting declarations. There are no conflicting declarations if