跪拜 Guibai
← Back to the summary

Dart Proposals Conditional Parts and `concrete` to End Cross-Platform Implementation Boilerplate

Dart language #4827:First version of conditional part feature specification PR is great, especially useful for Flutter cross-platform plugins, FFI, and platform adaptation layers.

The proposal mainly brings two changes:

In other words, Dart wants different platforms to share the same static type and API checking results, only replacing the concrete implementation at compile time, which is very Kotlin-like, hahaha.

Currently the PR is still in Open state, the specification is marked as Draft, added 624 lines of proposal documentation, and has not yet officially modified Dart SDK code, but it's definitely worth looking forward to.

For example, Dart currently supports:

import 'channel_stub.dart'
    if (dart.library.io) 'channel_io.dart'
    if (dart.library.js_interop) 'channel_web.dart';

The problem is that channel_io.dart and channel_web.dart belong to different libraries. If you have a Channel class that maintains private state like _name, _handle, and want IO and Web to implement its close() method separately, the existing solution requires splitting the interface and implementation classes, then calling through factories or proxies. Dart does not guarantee that the APIs of the two conditional import files are completely consistent.

With the new proposal, you can write directly:

// channel.dart
part 'channel_stub.dart'
    if (dart.library.io) 'channel_io.dart'
    if (dart.library.js_interop) 'channel_web.dart';

abstract class Channel {
  final String _name;

  Channel(this._name);

  concrete void close();
}

Then in the native platform file:

// channel_io.dart
part of 'channel.dart';

import 'dart:io';

augment class Channel {
  augment void close() {
    stdout.writeln('Closing $_name');
  }
}

There are actually three changes here:

Why introduce concrete specifically? I think this is the most interesting point of the proposal. For example, in Dart, an abstract class:

abstract class Channel {
  void close();
}

means close() is an abstract method, so if a subclass calls super.close();, it will cause a compile error because there is no callable method body in the parent class.

But if the parent's method body is provided via Conditional Part, the Analyzer doesn't read the platform implementation when analyzing public declarations, so how does it know whether close() ends up being abstract?

So the proposal introduces:

abstract class Channel {
  concrete void close();
}

This explicitly tells the Analyzer: this method has a concrete implementation, can be inherited by subclasses, and also allows super.close().

At compile time for a specific platform, it checks whether the corresponding augment actually fills in the method body; if not, it reports a compile error. Again, Kotlin vibes.

Additionally, concrete supports non-const generative constructors, allowing platform parts to provide corresponding initialization logic.

Most importantly, platforms can change implementations but cannot casually alter the type structure. To ensure consistent static analysis results across platforms, the proposal imposes relatively strict restrictions on Conditional Parts:

Behavior Allowed in Conditional Part?
Filling in implementation for existing methods Allowed
Accessing private members of the same library Allowed
Independent imports of platform dependencies Allowed
Adding top-level declarations, including private ones Forbidden
Introducing new member declarations to a class Forbidden
Adding extends, implements, with Forbidden
Adding enum values Forbidden
Modifying default values of optional parameters Forbidden
Completing const constructors in Conditional Part Forbidden

Although new member declarations cannot be introduced, it still allows certain conforming augment variable declarations to complete existing properties and provide underlying storage, not completely prohibiting platform-differentiated instance state.

In other words, the Analyzer can first analyze only the public declarations, obtaining stable type relationships, method signatures, enum sets, and constant results. Then, when the compiler selects a specific platform, it applies the augment from the corresponding part to the declarations, allowing implementation completeness and type checks. This ensures that different platforms cannot secretly change the static structure of the public API.

This is very useful for Flutter, for example:

So I'm looking forward to this direction, especially as Dart increasingly needs to simultaneously handle Flutter Native, Web, Wasm, and various platform extensions, maintaining a consistent Dart API is important.

If Augmentations also land officially later, then Dart's part is no longer just a simple file-splitting mechanism, but can become a more complete cross-file, cross-platform implementation composition mechanism.

Perfect!

Comments

Top 2 from juejin.cn, machine-translated. The original thread is authoritative.

栈与烟火

To be honest, Flutter's progress is much slower than expected.

茶树米

It's taken this many years to only now do this; the enthusiasm to follow up probably won't be strong, will it?