mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 00:45:16 +00:00
Add extension types to the CHANGELOG.
Change-Id: Id3bf8bd8b0a366ce58eb9070f0e2fa15d55bf07d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/350002 Reviewed-by: Alexander Thomas <athom@google.com> Reviewed-by: Leaf Petersen <leafp@google.com> Commit-Queue: Alexander Thomas <athom@google.com> Reviewed-by: Erik Ernst <eernst@google.com> Auto-Submit: Bob Nystrom <rnystrom@google.com>
This commit is contained in:
parent
4c8ae57d93
commit
f9266472c0
91
CHANGELOG.md
91
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(<Meters>[] is List<int>); // Prints "true".
|
||||
|
||||
// An explicit cast is allowed and succeeds as well:
|
||||
List<Meters> meterList = <int>[1, 2, 3] as List<Meters>;
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue