Learning Dart
上QQ阅读APP看书,第一时间看更新

Variables – to type or not to type

In our first example (Raising rabbits) in Chapter 1, Dart – A Modern Web Programming Language, we started by declaring a variable rabbitCount dynamically with var, and then in the second version we gave it a static type int (refer to the file prorabbits_v1.dart and prorabbits_v2.dart in Chapter 1, Dart – A Modern Web Programming Language) and concluded that typing is optional in Dart. This seems confusing and has provoked a lot of discussion: "is Dart a dynamic language like Ruby or JavaScript, or a static language like Java or C#?" After all, some of us were raised in the (static) belief that typed variables are absolutely necessary to check if a program is correct, a task mainly performed by the compiler (but the Dart VM has no separate compiler step, and dart2js, the Dart to JS compiler, doesn't check types because JS is fully dynamic).

It turns out that no mainstream language actually has a perfect type system (static types don't guarantee program correctness) and that not letting a program run because of an obscure type error blocks the programmer's creativity; however, it is true that static type checks can prevent bugs. On the other hand, the dynamic belief states that typing variables hinders the programmer's fantasy (and wears out the fingers). In their world, grave errors due to wrong types occur only when the program is running; to avoid such calamities, they have to rely on a rigorous unit testing discipline.

Dart takes a pragmatic middle stand: web programming is already accustomed to dynamically typed languages that are more flexible, and Dart honors that and adheres to a system of optional or loose typing. You can start out writing your code without types while you're still experimenting and doing quick prototyping. In a more advanced stage, you can annotate (some of) the code with types. This will enhance the readability of your code for your fellow developers and as such it is additional documentation. Furthermore, it allows the Dart analyzer tools to check the code for the types used and report possible errors, so it makes more useful developer tools possible.

As the app becomes larger and more stable, types can be added to aid debugging and impose structure where desired, making the code more robust, documented, and more maintainable. Dart code can evolve from a simple, untyped experimental prototype to a complex, modular application with types. Moreover, as you will experience while working in the Dart Editor, with types the IDE (Integrated Development Environment) can suggest better autocompletion for the properties and methods of any code object. The two extremes (no typing or everything typed) are not encouraged.

Tip

In general, give everything in your code that can be seen publicly a type (in the sense that it is visible in and can be used from outside code, sometimes called the interface), such as top-level variables and functions including their arguments. That way, other apps can use your code with increased safety.

Using var (or final or const) for an object leaves it untyped, but in fact Dart internally considers this object to be of type dynamic, the unknown type. The keyword dynamic is very rarely used in code.

To cope with this dilemma, Dart has two runtime modes (ways of executing programs):

  • Checked mode: This is typically used when you develop, debug, and test. The IDE will warn you when you misuse variables in a typed context (a tool called the dart-analyzer continuously checks your code, while saving and even while you type). The types are checked when executing assignments, when passing arguments to a function, and when returning a result from a function. By default your program is also run in this mode, breaking the execution when a (typing) error occurs (you can change this behavior by navigating to Run | Manage Launches | VM settings and unchecking the Run in checked mode checkbox).
  • Production mode: This is when your program runs for real, that is, it used by customers. Then Dart runs as a fully dynamic language and ignores type information, giving a performance boost because the checks don't need to be performed.

Errors (indicated in the Editor by a white x in a red circle) prevent you from running the program. For example, delete an ending ; or } from some source code and see what happens.

Warnings (a black ! in a yellow triangle) indicate that the code might not work. For example, in the following code snippet (from chapter_2\checked_mode.dart), a warning is indicated in line (1):

int age = 'Dart';                  (1)
print('$age');

The warning sign is shown in front of the line and the string Dart is yellow underlined. If you hover the cursor over one of these, you see the message: A value of type 'String' is not assignable to 'int'. If you try to run this example in the default checked mode in Dart Editor, you'll get the following output:

Unhandled exception:
type 'String' is not a subtype of type 'int' of 'age'.
#0  main (file:///E:/dart/Book/code/chapter_2/checked_mode/bin/checked_mode.dart:2:14)

But if you uncheck and let it run in production mode, it runs and the normal output Dart appears in the console. Dart expects the developer to have thoroughly checked and tested the program:

Note

Warnings do not prevent you from running a program in production mode in most cases.

A variable is just a nickname for an object, the same object can have multiple variables referring to it, and a variable name can also switch from one object to another, such as in the following code:

var name = 'John';
name = 'Lucy';    // name now refers to another String object

But sometimes you don't want this to happen; you want a variable to always point at the same object (such as immutable variables in functional languages or in other words a read-only variable). This is possible in Dart using the keyword final, such as in the following code (refer to final.dart):

final name = 'John';
name = 'Lucy';         //   (1)  warning!

Now, line (1) generates a warning: Final variables cannot be assigned a value, but the execution is even stopped in production mode! The keywords var and final used as such both refer to a dynamic untyped variable, but final can be used together with a type, as shown in the following code:

final String name = 'John';

The const keyword (that we used already in prorabbits_v2.dart in Chapter 1, Dart – A Modern Web Programming Language) like final also refers to something whose value cannot change, but it is more restricted. It must be a literal value (such as 100 or Dart or another const) or a calculation with known numbers, a so-called compile-time constant. For example, see the following code:

const NO_SECINMIN = 60;
const NO_SECINDAY = NO_SECINMIN * 60 * 24;

The following is an example that shows the difference:

int daysInWeek = 7;
final fdaysInYear = daysInWeek * 52;
const DAYSINYEAR =  daysInWeek * 52;  //  (2) error!

Now, line (2) gives an error:

'const' variables must be constant value.

In summary, types are not used for performance optimization and they don't change program behavior, but they can help you write and maintain your code; a little typing goes a long way. A combination of tool support, static analysis, checked mode assertions, and unit tests can make you feel just as safe in Dart as in any statically typed language, yet more productive.