🦺

Null safety codelabやってみた

5 min read

FlutterをやっていてDartのNull safetyがいまいち分からなかったので、公式が出しているcodelabをやってみました。こちらのcodelab、問題はあるものの解答らしきものはなかったので、自分の解答も合わせて書いておきます。

間違いなどありましたら、ご連絡いただければさいわいです!

今回やったもの

https://dart.dev/codelabs/null-safety

  • こちら英語でまだ決まった訳があるのか分からない単語も多かったため、そうした単語は原文のまま使っています。
  • 全文を訳すことはしておらず重要と思われるところのみ適宜まとめつつ訳しています。
  • 「コメント:」とあるのは筆者が思ったことや考えたこととなります。

null-safe type systemについて

  • 導入: Dart 2.12

null safetyを導入すると明示的に指定しない限り、コード中で変数にnullを入れることができないようにできる。

コメント: ぬるぽとおさらばできるのはありがたいですね。

このcodelabで学べること

  • Nullable型とnon-nullable型について
  • nullを許容するかどうかを示す?!をいつ使うか
  • 構文解析とtype promotionのフロー
  • late演算子が変数と初期化に与える影響

コメント: type promotionってなに?

Nullable型とnon-nullable型について

null safety導入後はすべての型でnon-nullableがデフォルトになり、以下のanullを代入しようとするとコンパイルエラーになります。

void main() {
  int a;
  a = null; // Null can't be assigined
  print('a is $a.');
}

※コード内コメントは筆者が追記。

nullを許容したい場合は?を使って明示する必要があります。

  • 解答
void main() {
  int? a;
  a = null;
  print('a is $a.'); // a is null
}

Nullable type parameters for generics

ジェネリクスの型パラメータも同じくnullを許容するかどうかを指定できます(デフォルトはnon-nullable)

Exercise: Nullable type parameters for generics

  • 解答
void main() {
  List<String> aListOfStrings = ['one', 'two', 'three'];
  List<String>? aNullableListOfStrings;
  List<String?> aListOfNullableStrings = ['one', null, 'three'];

  print('aListOfStrings is $aListOfStrings.');
  print('aNullableListOfStrings is $aNullableListOfStrings.');
  print('aListOfNullableStrings is $aListOfNullableStrings.');
}

こちらの練習問題ですが、ポイントが2つあります。

  • Listそのものがnullであることを許容させたい
  • Listの要素にnullがあることを許容させたい

前者の対応するのが以下のコードです。

List<String>? aNullableListOfStrings;

後者はこちら。List要素の型がStringで、?がつくことによって要素がnullの場合を許容させています。

List<String?> aListOfNullableStrings = ['one', null, 'three'];

The null assertion operator (!)

  • nullable typeがnullでないことを表現したいときは!を使う

  • 解答

int? couldReturnNullButDoesnt() => -3;

void main() {
  int? couldBeNullButIsnt = 1;
  List<int?> listThatCouldHoldNulls = [2, null, 4];

  int a = couldBeNullButIsnt;
  int b = listThatCouldHoldNulls.first!; // first item in the list
  int c = couldReturnNullButDoesnt()!.abs(); // absolute value

  print('a is $a.');
  print('b is $b.');
  print('c is $c.');
}

couldReturnNullButDoesnt()関数とlistThatCouldHoldNullsint?とあるので、nullableな型になっています。
以下でそれぞれから値を取得しようとしたときにint型はnon-nullableな値のため、このままだとエラーになってしまいます。
なので、!を使ってnullじゃないことをプログラムに教えてあげればOKです。

Type promotion

  • nullになる可能性がある変数でも、nullにならないのであれば、non-nullableな変数として扱う

  • Definite assignment : Dartでは変数がどこで割り当てられ読み込まれたかを追跡でき、non-nullableな変数に値が代入されているかを検証できる。

以下のif-else文をコメントアウトすると、textに値が代入されないままprint(text)で使用されるためエラーになります。

void main() {
  String text;

// 以下のif文をコメントアウトすると、エラーになる
  if (DateTime.now().hour < 12) {
   text = "It's morning! Let's make aloo paratha!";
  } else {
   text = "It's afternoon! Let's make biryani!";
  }

  print(text);
  print(text.length);
}

lateキーワード

クラス変数やトップレベル変数はnon-nullableであるべきだが、値をすぐには代入できない場合がある。そういうときにはlateを使います。

  • late

    • あとで代入することを宣言できる。non-nullableな型で代入してなくてもエラーにならない
    • 代入されずに値が使用されるとLateInitializationErrorを投げる
    • latefinalと一緒に使うこともできる

  • 解答

class Meal {
  // lateをつけることで、あとから代入することを明示
  late String _description;

  set description(String desc) {
   _description = 'Meal description: $desc';
  }

  String get description => _description;
}

void main() {
  final myMeal = Meal();
  myMeal.description = 'Feijoada!';
  print(myMeal.description);
}
  • finalと使う
class Team {
  late final Coach coach;
}

class Coach {
  late final Team team;
}

void main() {
  final myTeam = Team();
  final myCoach = Coach();
  myTeam.coach = myCoach;
  myCoach.team = myTeam;

  print('All done!');
}

lazy initialization

lateをつけた変数は使用するときに初めて初期化されます
codelabのコードでみてみます。

_cachelateをつけないで実行する。

int _computeValue() {
  print('In _computeValue...');
  return 3;
}

class CachedValueProvider {
  final _cache = _computeValue();
  int get value => _cache;
}

void main() {
  print('Calling constructor...');
  var provider = CachedValueProvider();
  print('Getting value...');
  print('The value is ${provider.value}!');
}

🔽

Calling constructor...
In _computeValue...
Getting value...
The value is 3!

_cachelateをつけて実行する。

class CachedValueProvider {
  late final _cache = _computeValue();
  int get value => _cache;
  int _computeValue() {
      print('In _computeValue...');
      return 3;
   }
}

void main() {
  print('Calling constructor...');
  var provider = CachedValueProvider();
  print('Getting value...');
  print('The value is ${provider.value}!');
}

🔽

Calling constructor...
Getting value...
In _computeValue...
The value is 3!

前後の差分を取ってみます。

@@ -1,4 +1,4 @@
 Calling constructor...
-In _computeValue...
 Getting value...
+In _computeValue...
 The value is 3!

lateをつける前はコンストラクタでインスタンスを生成したあとに_computeValue()が呼び出されています。
lateをつけたあとは、getterで_cacheを取得するときに_computeValue()が呼び出されて値が初期化されていることがわかります。

参考

Discussion

ログインするとコメントできます