dart-lang / dart-lang/language

Non-interface class declarations.

Open
#704 13 comments 11 reactions 0 assignees View on GitHub
feature
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

As a simple approach to sealing classes (#349), I propose a minimal, yet powerful, feature: Non-interface classes.

## Background

Dart classes are "Kotlin open" classes *and* interfaces. It means that anyone can implement the interface of a class and extend the class—at least if it has a public generative constructor.

That may sometimes be more affordance than desired. There are requests for "Sealed" classes intended to prevent other users from implementing or extending a class. The reasons given for that feature is to control dependencies, so that additions are not breaking changes, and to enable performance improvements when compilers can locally deduce properties of *all* implementations of a type.

## Proposal: Non-Interface `struct` Classes

Introduce `struct` as a modifier on class and mixin declarations, `struct class` and `struct mixin`, which makes the class name not denote an interface. It still introduces a *type*, and a *class*/*mixin*, but not an *interface name*.

The class has an "interface" in the sense of a set of implemented types and a mapping from member names to member signatures (which is what the language uses to see if a member access is allowed), but the name will not be usable in `implements` clauses.

A `struct class` can be *extended* as long as it has a public generative constructor. Such a subclass of a `struct class` **must** also be a `struct class`.

If a `struct mixin` is applied in on any allowable super-type, the result is a `struct` class.

We allow a lone `struct` to work as a shorthand for `struct class` whenever `struct` is not immediately followed by `class` or `mixin`. This reduces the extra typing when declaring a `struct class`.

### Examples
If we combine this with improved default constructors (#469), then you can declare a simple data class as:
```dart
struct Point {
final int x, y;
}
```
It's possible to extend the class, but not implement it.

A properly sealed class would be:
```dart
struct Point {
final int x, y;
Point._(this.x, this.y);
factory Point(int x, int y) = Point._;
}
```
Maybe we can even find a way to avoid the intermediate constructor, say `factory Point._(this.x, this.y);` would be a generative constructor which cannot be used as such. (Probably not the best syntax, but something to start from).

### Benefits

The benefit of a non-interface class is that it is guaranteed that all objects matching the type will also *extend* the class (or mix in the mixin). That ensures that private members are available, and that invariants ensured by the constructor are maintained.

Together with only having a private generative constructor, this ensures that nobody else can implement the class.

If it's possible to restrict the availability of a subclass with a public generative constructor, say to the same package using package-local libraries and not exporting the subclass from a public library, it allows a class that can only be extended locally in the same package.

To ensure that specific methods are not changed, we will also need to be able to make methods final (non-overridable).

### Drawbacks

A class that doesn't have an implementable interface, cannot be mocked. It also cannot be implemented for any other reason, which is a restriction compared to the flexibility provided by all current Dart classes.

Changing a class into a `struct class` is a breaking change. This makes it impossible to apply the `struct` to existing platform classes when the feature is implemented. Packages can increment their major version number and add `struct` where they prefer to have it. For clients who are not depending on implementing the package's interface, it's free to update to the new version, and everybody else gets a fair warning where things are going.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.