Что такое ключевое слово external в Dart и где оно применяется?

Ответ

Ключевое слово external в Dart используется для объявления функции или метода, реализация которого предоставляется вне Dart-кода — обычно в нативной платформе (через dart:ffi) или в платформо-специфичных имплементациях Dart SDK.

Основные сценарии использования в моей практике с Flutter:

  1. Интеграция с нативным кодом через dart:ffi
    
    import 'dart:ffi';
    import 'package:ffi/ffi.dart';

// Объявление external функции из нативной библиотеки final DynamicLibrary nativeLib = DynamicLibrary.open('libnative_audio.dylib');

// External функция, реализованная в C/C++ external int processAudioFrame( Pointer inputBuffer, Pointer outputBuffer, int frameSize, );

// Использование в Dart-коде void processAudio(List input) { final inputPtr = calloc(input.length); final outputPtr = calloc(input.length);

for (var i = 0; i < input.length; i++) { inputPtr[i] = Float(input[i]); }

final result = processAudioFrame(inputPtr, outputPtr, input.length);

// Обработка результата... calloc.free(inputPtr); calloc.free(outputPtr); }


2. **Платформо-специфичные реализации в Dart SDK**
При изучении исходного кода Dart SDK можно встретить такие конструкции:
```dart
// В dart:io
abstract class File {
  external factory File(String path);

  external Future<RandomAccessFile> open({FileMode mode = FileMode.read});

  // Реализация предоставляется отдельно для каждой платформы
  // через аннотацию @patch в patch-файлах
}
  1. Генерация кода через аннотации

    // В библиотеках для кодогенерации
    @JsonSerializable()
    class User {
    final String name;
    final int age;
    
    User(this.name, this.age);
    
    // External factory, реализация генерируется build_runner
    external factory User.fromJson(Map<String, dynamic> json);
    
    external Map<String, dynamic> toJson();
    }

Ограничения и особенности:

  • external члены не могут быть async, sync*, или async*
  • Реализация должна быть предоставлена во время выполнения
  • Часто используется с аннотацией @patch в исходном коде Dart SDK
  • Требует точного соответствия сигнатур между объявлением и реализацией

Практический пример из Flutter-разработки: При создании плагина для Flutter, который использует нативные API iOS/Android, external методы позволяют объявить интерфейс в Dart, в то время как реализация предоставляется в Objective-C/Swift или Java/Kotlin через механизм платформенных каналов (Platform Channels) или dart:ffi.

Ответ 18+ 🔞

А, ну вот, про external в Dart! Это же, блядь, такая штука, которая как будто говорит: «Слушай, дружок, реализация этой функции где-то там, за горами, за лесами, в нативном коде, а я тут просто красивая вывеска». Ёпта, прям как обещание политика перед выборами — звучит здорово, а где конкретика — хуй его знает.

Основные сценарии, где эта мартышлюшка вылезает:

  1. Когда надо пообщаться с нативным кодом через dart:ffi Тут вообще история отдельная. Представь: у тебя есть какая-нибудь библиотека на C, которая делает что-то офигенное, например, аудио обрабатывает со скоростью света. А ты в своём уютном Dart-мирке сидишь. Как до неё достучаться? Вот тут и появляется наш external, как волшебная дверь в другой мир.

    import 'dart:ffi';
    import 'package:ffi/ffi.dart';
    
    // Открываем нативную библиотеку, как банку пива после работы
    final DynamicLibrary nativeLib = DynamicLibrary.open('libnative_audio.dylib');
    
    // А вот и наша загадочная external-функция. Где её тело? Да в той самой C-шной библиотеке!
    external int processAudioFrame(
      Pointer<Float> inputBuffer,
      Pointer<Float> outputBuffer,
      int frameSize,
    );
    
    // Использование в Dart-коде
    void processAudio(List<double> input) {
      final inputPtr = calloc<Float>(input.length);
      final outputPtr = calloc<Float>(input.length);
    
      for (var i = 0; i < input.length; i++) {
        inputPtr[i] = Float(input[i]);
      }
    
      // И тут мы вызываем эту магию. Вжух — и выполнение уходит в нативный код.
      final result = processAudioFrame(inputPtr, outputPtr, input.length);
    
      // Обработка результата...
      calloc.free(inputPtr);
      calloc.free(outputPtr);
    }

    Чувствуешь? Мы как бы говорим Dart: «Поверь мне на слово, такая функция есть. Когда дойдёт до дела — я тебе её подсуну». Доверия ебать ноль, но система работает.

  2. Внутри самого Dart SDK, для платформо-специфичных вещей Если когда-нибудь полезешь в исходники самого Dart (что само по себе приключение), увидишь такое. Вот, например, класс File из dart:io. Он же на разных операционках по-разному работает! Ну не может один и тот же код и на Windows, и на Linux, и на macOS файлы одинаково открывать. Поэтому там внутри делают так:

    abstract class File {
      // Смотри-ка, external factory! То есть сама логика создания объекта File — где-то снаружи.
      external factory File(String path);
    
      // И открытие файла — тоже external. Реализация спрятана в «патчах» для каждой ОС.
      external Future<RandomAccessFile> open({FileMode mode = FileMode.read});
    }

    Это гениально и немного по-пидорски. Вместо того чтобы городить if (windows) {...} else if (linux) {...}, они просто объявляют интерфейс, а потом в отдельных файлах-заплатках (@patch) подсовывают правильную реализацию под каждую платформу. Хитрая жопа, не спорю.

  3. В кодогенерации, когда реализацию напишет за тебя машинка Любишь аннотации вроде @JsonSerializable()? Так вот, под капотом там может использоваться external как раз для того, чтобы сказать: «Эй, кодогенератор, вот тут будет метод fromJson, но я его писать не буду — ты его сгенерируй сам, умница».

    @JsonSerializable()
    class User {
      final String name;
      final int age;
    
      User(this.name, this.age);
    
      // External factory. Когда запустишь build_runner, он увидит эту аннотацию
      // и нагенерирует тебе реальную реализацию этого метода где-то в глубинах .g.dart файла.
      external factory User.fromJson(Map<String, dynamic> json);
    
      external Map<String, dynamic> toJson();
    }

    Удобно, правда? Написал заголовок, а всю грязную работу за тебя делает автоматика. Лень — двигатель прогресса, ёпта.

Что нельзя и на что обратить внимание (чтоб не обосраться):

  • Функция с external не может быть async, sync* или async*. Она должна быть прямой и честной, без этих ваших потоковых извращений.
  • Это не магия. Реализация обязательно должна быть предоставлена где-то в другом месте, иначе в рантайме получишь ядрёную вошь в виде ошибки.
  • Сигнатура (типы аргументов и возвращаемого значения) должна идеально совпадать между объявлением в Dart и реальной реализацией в нативном коде. Одно несовпадение — и пиzдец, приехали.

Из практики Flutter-разработки: Допустим, пишешь ты плагин, которому нужно зажечь вспышку на камере. API для этого — сугубо нативное, на Java/Kotlin для Android и на Obj-C/Swift для iOS. Ты в Dart-части плагина объявляешь external-метод, который будет вызывать нативную функцию через dart:ffi или через старые добрые Platform Channels. Получается такой мостик между мирами. Dart кричит: «Эй, Андроид, включи вспышку!», а external — это та самая труба, по которой этот крик доходит.

Короче, external — это как дипломатический паспорт для функций. Сама функция живёт в другой стране (в нативном коде или в сгенерированном коде), но у неё есть права приходить и работать на территории Dart. Главное — чтобы паспорт был настоящий, а не хуй в пальто.