Variables and Optional Types in Dart

Update: Reworked some of the optional types section based on comments.

Intro

Dart is a structured web programming language, complete with libraries, a virtual machine, and an editor. Dart is designed to help developers create more complex and feature rich apps for the modern web.

In this post, we look at variables and their types in Dart.

Variables

The most simple example declares a variable without an explicit type:

main() {
  var msg = 'hello';
  print(msg);
}
> hello

The most common way to declare a variable is to use the var keyword. This is truly a variable, as shown here:

main() {
  var msg = 'hello';
  msg = 'world';
  print(msg);
}
> world

final

So far, this is a lot like JavaScript. Let's see what Dart can do differently. Dart allows you to mark variables as final.

main() {
  final msg = 'hello';
  msg = 'world';  // ERROR: cannot assign value to final variable
  print(msg);
}

This all looks simple, but let's reflect on what we've seen. In the above examples, there are no classes. While Dart supports classes, single inheritance, and interfaces, they are not required to write Dart code. Plenty of Dart code is written with minimal or zero classes.

static

Having said that, we're going to introduce a class to illustrate the next concept.

class Bar {
  static x = 4;
}

main() {
  print(Bar.x);
}
> 4

In Dart, variables can also be static to a class. Like in Java, a static variable is associated to the class and not the instance. Notice how, in the above example, we don't need an instance of the Bar class in order to access the x variable.

A common pattern is to make static variables final, usually to declare enumerations or human readable names for codes.

class States {
  static final ON = 1;
  static final OFF = 0;
}

main() {
  print(States.ON);
}
> 1

We will cover classes in depth in a future blog post.

Privacy

Dart has two levels of privacy for its variables: public and private to its library. We will cover how private works when we introduce libraries in another blog post. For now, everything you see is public.

You might see variables with names prefixed with underscore like _foo. The underscore marks the variable as private to the library it was declared in. More on this soon.

Optional Types

All the variables so far have not declared a type. Dart supports optional typing, and treats type declarationss basically like annotations for tools and documentation. Type declarations do not affect the running code.

To start, let's look at a simple untyped example:

multiply(x, factor) {
  return x * factor;
}

main() {
  print( multiply(4, 2) );
}
> 8

This of course works, but imagine importing this code into your project. It would be helpful to know what exactly you can pass to the multiply method, giving tools the option to help you or get warnings earlier. So we can add types:

multiply(num x, num factor) {
  return x * factor;
}

main() {
  print( multiply(4, 2) );
}
// 8

Now the expectations of multiply are more clear, and with more clarity comes easier collaboration with larger teams and more useful tools. We can even catch errors earlier. Consider this code:

multiply(num x, num factor) {
  return x * factor;
}

main() {
  print( multiply(4, "hello") ); // WARNING at this line, string isn't assignable to num
}
> NaN // because you can't multiple a string with a number

With the types specified, we can see a warning at the line with the problem. If we didn't have the types specified in multiply, we would still get an error, but we wouldn't get the early warning.

Remember, though, that while types are great annotations for tools and teams, the type declarations don't affect running code.

For example, consider this wacky code:

main() {
  num msg = 'hello'; // WARNING: String is not assignable to num
  print(msg);
}
> hello  // still runs!

We've replaced the var keyword with the num type declaration. We also did a crazy and assigned a string to what we declared was a number. In more traditional languages, this would have stopped the program in its tracks. However, for Dart, a warning can be reported by the compiler but the program runs just fine.

Does this mean that Dart can run any program no matter how messed up it is? Of course not, if you write code that makes no sense, it will fail to run:

main() {
  num x = "hello";
  x = x / 2; // NoSuchMethodException
  print(x);
}

What's important to remember is that if you write code that can run, it will run, even if the type declarations aren't correct.

To language purists, this sounds awful, for how can you have a programming language with an unsound type system!?!?! Back in the real world, though, we encounter languages with unexpressive type systems everywhere. Every time you perform a cast in your favorite language, you are expressing something your type system can't handle.

Dart is a fan of optional types because they reduce programmer friction. Also important to remember, the web programmer is very much used to a dynamic type system, so forcing static mandatory types in a language bound for the web would hinder adoption.

Types aren't just ephemeral concepts, though. Running Dart programs actually do know what the types of its variables are. For example:

main() {
  print(4 is String);        // false
  print("hello" is String);  // true
  print(4 is Dynamic);       // true
  print(4 is num);           // true

  var x = 1;
  print(x is Dynamic);       // true
  print(x is num);           // true
}

The is check is not affected by the declared types, instead it is checking what the type of the variable actually is.

As you can see in the above code, variables always appear as Dynamic, in order to interoperate with code that did not specify its types. The var keyword is shorthand for the Dynamic type, aka the unknown type.

Built-in types

Dart provides Object as the super type. You'll also find:

  • Dynamic (used when no static type is provided)
  • String
  • bool
  • num
    • int
    • double
  • Collection
    • List
    • Set
  • Map
  • others...

Summary

Dart supports variables with optional typing, defaulting to the Dynamic type if no static type annotation is declared. Variables can be marked as final, which means its value cannot be altered. Classes themselves can have variables if they are marked as static.

Dart's optional typing means the program will run with or without declared types, and even if the type declarations are incorrect. Dart variables and objects have types at runtime, however.

Next Steps

Learn about functions in Dart.

Popular posts from this blog

The 29 Healthiest Foods on the Planet

Lists and arrays in Dart

Converting Array to List in Scala