Можно ли объявить локальную final переменную в Dart без немедленного присваивания значения?

«Можно ли объявить локальную final переменную в Dart без немедленного присваивания значения?» — вопрос из категории Dart Core, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, в Dart можно объявить локальную final переменную без немедленной инициализации, но с важным условием: значение должно быть присвоено ровно один раз до первого использования. Компилятор проводит статический анализ потока управления (flow analysis), чтобы это гарантировать.

Корректные примеры:

// 1. Присваивание в условии
void processUser(bool isPremium) {
  final String accessLevel;

  if (isPremium) {
    accessLevel = 'premium';
  } else {
    accessLevel = 'basic';
  }

  print('Access: $accessLevel'); // OK
}

// 2. Присваивание в try-catch
void loadConfig() {
  final String config;

  try {
    config = _readConfigFile();
  } catch (e) {
    config = 'default';
  }

  print('Config: $config'); // OK
}

// 3. Позднее присваивание в цикле (сложный случай)
void findFirstEven(List<int> numbers) {
  final int firstEven;
  bool found = false;

  for (final number in numbers) {
    if (number % 2 == 0) {
      firstEven = number;
      found = true;
      break;
    }
  }

  if (!found) {
    firstEven = -1; // Обязательно присвоить в ветке else
  }

  print('First even: $firstEven'); // OK
}

Ошибки, которые вызовет компилятор:

// ОШИБКА: использование до присваивания
void errorExample1() {
  final int x;
  print(x); // Error: Non-nullable variable 'x' must be assigned
  x = 10;
}

// ОШИБКА: не все пути выполнения присваивают значение
void errorExample2(bool condition) {
  final String message;

  if (condition) {
    message = 'true';
  }
  // Нет else-ветки - ошибка если condition == false

  print(message); // Error
}

// ОШИБКА: повторное присваивание
void errorExample3() {
  final int value = 10;
  value = 20; // Error: 'value' can only be set once
}

Отличия от полей класса:

class User {
  // Для полей класса final требует немедленной инициализации:
  final String name = 'John'; // OK
  final int id; // Error если не инициализировано

  // Или инициализация в конструкторе:
  User(this.id); // OK

  // Для late final можно отложить инициализацию:
  late final String token;
  void setToken(String t) {
    token = t; // Можно присвоить один раз
  }
}

Практическое применение в Flutter:

Widget buildUserCard(User user) {
  // Позднее связывание final переменных полезно для вычислений
  final Color cardColor;
  final IconData icon;

  if (user.isAdmin) {
    cardColor = Colors.red[100]!;
    icon = Icons.admin_panel_settings;
  } else if (user.isPremium) {
    cardColor = Colors.blue[100]!;
    icon = Icons.star;
  } else {
    cardColor = Colors.grey[100]!;
    icon = Icons.person;
  }

  return Card(
    color: cardColor,
    child: ListTile(
      leading: Icon(icon),
      title: Text(user.name),
    ),
  );
}

Это мощная особенность Dart, которая повышает безопасность кода, гарантируя, что final переменные будут инициализированы перед использованием, но давая гибкость в выборе момента инициализации.