Dart 2: Legacy of the `void`

Matan Lurey
Aug 30, 2018 · 4 min read
A semi-accurate depiction of the universe of void-like types in Dart2

One of the questions I see the most asked on StackOverflow, Gitter, and even Google-internal support channels is the difference between the following built-in types in Dart 2: Object, dynamic, void, and Null.

Long-story short, Null (or Bottom in other languages, i.e. “Nothing”) shouldn’t be used in most real user-code, and I suspect we’ll see more articles and lints in the near future to gently discourage usage.

The rest of the three are not as clear, because something in Dart 2 anything can be dynamic, Objector voidat runtime, varying only by the static type signature. So let’s look at a few practical examples of when you should use which type signature.


Object

Objectis the root class of the Dart class hierarchy, and every other class in Dart is a sub-class of Object — including “primitive” types like int, double, or bool. It guarantees a few things: a hashCodeproperty, an ==operator, a toStringmethod.

Practically speaking, I use Objectlike a poor man’s union type — expecting users to use the isoperator to determine the real type of something before using it. I don’t use dynamic, because, as outlined in the next section, it disables important static analysis and more easily allows you to get into an invalid state.

Another option is to use Object to declare you don’t care what the inner type of a data structure is, for example List<Object> might mean “a list of anything”. This comes in handy when, for example, writing a function that combines the hashCode of every element in a List:

A nice property of Object(compared with dynamic) is that you will get immediate analysis and compiler feedback if you try to call a method on it that doesn’t reliably exist. For example, the following produces a static error:

In practice though, Object is fairly(and intentionally) limited. My hope is that Dart will get support for method overloads and that will allow me to dramatically decrease my usage of the Object type in real code.

dynamic

I personally never use the dynamic type in Dart 2. From my perspective, it is sort of a union of Objectand a special instruction that tells tools and compilers to disable static analysis checks. That is, the following is legal, and will only present an error at runtime (not statically!):

In Dart 1, dynamic was everywhere, and any other static type was for IDE and static analysis support — but the compiler (and runtime) treated everything as dynamic. There are still some unfortunate “gotchas” in Dart 2 that can accidentally create a dynamic-typed variable, though:

Worse yet, dynamic calls lose type information that is vital in Dart 2:

The reason for this error is because the actual call here is:

… which does not have enough static type information to produce a Iterable<String>. By fixing users to have the proper type (and avoiding dynamic calls), everything works:

void

Lastly, we have void, the newest type in Dart 2. In Dart 1 void was only usable as the return type of a function (such as void main()), but in Dart 2 it has been generalized, and is usable elsewhere, for example Future<void>.

A void type is semantically similar to Object(it could be anything), except with additional restrictions — a void type cannot be used for anything (even == or hashCode), and it is invalid to assign something to a void type:

In practice, I use void to mean “anything and I don’t care about the elements” or, more commonly, to mean “omitted”, such as in Future<void> or Stream<void>:

In the above code snippet, I don’t want users to try and use the return value of the provided Future, as it is not relevant. I’ve seen examples of using Future<Null>for this purpose, and that was actually a workaround before Future<void>was possible.

For example, this is statically OK, but at runtime is invalid in Dart 2:

… where as using Future<void>for doAThing()is valid and correct.

Another example might be a Stream that fires without any event data:

Another more practical use is implementing a class with generic type arguments you won’t be using. For example, implementing the popular Visitor pattern, we can ignore the C(context) type argument when it isn’t used by passing void:


I hope this brief article helps you with API decisions around using Object, dynamic, void. Leave comments if you have any other questions or ideas!

Dart

Dart is a client-optimized language for fast apps on any platform.

Thanks to Filip Hracek

Matan Lurey

Written by

Software engineer @Google and @Dart_Lang. Opinionated.

Dart

Dart

Dart is a client-optimized language for fast apps on any platform. Learn more at https://dart.dev.

Matan Lurey

Written by

Software engineer @Google and @Dart_Lang. Opinionated.

Dart

Dart

Dart is a client-optimized language for fast apps on any platform. Learn more at https://dart.dev.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store