Description
[Edit, mit-mit, Feb 2024 This feature launched in Dart 3.3; we have docs here.]
[Edit, eernstg: As of July 2023, the feature has been renamed to 'extension types'. Dec 2023: Adjusted the declaration of IdNumber
—that example was a compile-time error with the current specification.]
[Edit, mit: Updated the text here too; see the history for the previous Inline Class content]
An extension type wraps an existing type into a new static type. It does so without the overhead of a traditional class.
Extension types must specify a single variable using a new primary constructor syntax. This variable is the representation type being wrapped. In the following example the extension type Foo
has the representation type int
.
extension type Foo(int i) { // A new type that wraps int.
void function1() {
print('my value is $i');
}
}
main() {
final foo = Foo(42);
foo.function1(); // Prints 'my value is 42'
}
Extension types are entirely static. This means there is no additional memory consumption compared to using the representation type directly.
Invocation of a member (e.g. a function) on an extension type is resolved at compile-time, based on the static type of the receiver, and thus allows a compiler to inline the invocation making the extension type abstraction zero-cost. This makes extension types great for cases where no overhead is required (aka zero-cost wrappers).
Note that unlike wrapper creates with classes, extension types do not exist at runtime, and thus have the underlying representation type as their runtime type.
Extension types can declare a subtype relation to other types using implements <type>
. For soundness, implements T
where T
is a non-extension type is only allowed if the representation type is a subtype of T
. For example, we could have implements num
in the declaration of IdNumber
below, but not implements String
, because int
is a subtype of num
, but it is not a subtype of String
.
Example
// Create a type `IdNumber` which has `int` as the underlying representation.
extension type IdNumber(int i) {
// Implement the less-than operator; smaller means assigned before.
bool operator <(IdNumber other) => i < other.i;
// Implement the Comparable<IdNumber> contract.
int compareTo(IdNumber other) => this.i - other.i;
// Verify that the IdNumber is allocated to a person of given age.
bool verify({required int age}) => true; // TODO: Implement.
}
class Foo implements Comparable<Foo> {
@override
int compareTo(Foo other) => 1;
}
void main() {
int myId = 42424242; // Storing an id as a regular int.
myId = myId + 10; // Allowed; myId is just a regular int.
var safeId = IdNumber(20004242); // Storing an id using IdNumber.
myId = safeId + 10; // Compile-time error, IdNumber has no operator `+`.
myId = safeId; // Compile-time error, type mismatch.
print(safeId.verify(age: 22)); // Prints true; age 22 matches.
myId = safeId as int; // Extension types support type casting.
print(safeId.i); // Prints 20004242; the representation value can be read.
final ids = [IdNumber(20001), IdNumber(200042), IdNumber(200017)];
ids.sort();
print(ids);
dynamic otherId = safeId;
print(otherId.i); // Causes runtime error: extension types are entirely static, ..
// .. the static type is `dynamic`, and the dynamic type of `otherId` has no `i`.
}
Specification
Please see the feature specification for more details.
This feature realizes a number of requests including #1474, #40
Experiment
A preview of this feature is available under the experiment flag inline-class
.
Implementation tracking
Metadata
Metadata
Assignees
Type
Projects
Status