Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

dart.dev uses cookies from Google to deliver and enhance the quality of its services and to analyze traffic.

Learn more
The Dart type system

Dart 3.11 is live!Learn more

The Dart type system

Why and how to write sound Dart code.

The Dart language is type safe: it uses a combination of static type checking andruntime checks to ensure that a variable's value always matches the variable's static type, sometimes referred to as sound typing. Althoughtypes are mandatory, typeannotations are optional because oftype inference.

One benefit of static type checking is the ability to find bugs at compile time using Dart'sstatic analyzer.

You can fix most static analysis errors by adding type annotations to generic classes. The most common generic classes are the collection typesList<T> andMap<K,V>.

For example, in the following code theprintInts() function prints an integer list, andmain() creates a list and passes it toprintInts().

static analysis: failuredart
voidprintInts(List<int>a)=>print(a);voidmain(){finallist=[];list.add(1);list.add('2');printInts(list);}

The preceding code results in a type error onlist (highlighted above) at the call ofprintInts(list):

error - The argument type 'List<dynamic>' can't be assigned to the parameter type 'List<int>'. - argument_type_not_assignable

The error highlights an unsound implicit cast fromList<dynamic> toList<int>. Thelist variable has static typeList<dynamic>. This is because the initializing declarationvar list = [] doesn't provide the analyzer with enough information for it to infer a type argument more specific thandynamic. TheprintInts() function expects a parameter of typeList<int>, causing a mismatch of types.

When adding a type annotation (<int>) on creation of the list (highlighted below) the analyzer complains that a string argument can't be assigned to anint parameter. Removing the quotes inlist.add('2') results in code that passes static analysis and runs with no errors or warnings.

static analysis: successdart
voidprintInts(List<int>a)=>print(a);voidmain(){finallist=<int>[];list.add(1);list.add(2);printInts(list);}

Try it in DartPad.

What is soundness?

#

Soundness is about ensuring your program can't get into certain invalid states. A soundtype system means you can never get into a state where an expression evaluates to a value that doesn't match the expression's static type. For example, if an expression's static type isString, at runtime you are guaranteed to only get a string when you evaluate it.

Dart's type system, like the type systems in Java and C#, is sound. It enforces that soundness using a combination of static checking (compile-time errors) and runtime checks. For example, assigning aString toint is a compile-time error. Casting an object to aString usingas String fails with a runtime error if the object isn't aString.

The benefits of soundness

#

A sound type system has several benefits:

  • Revealing type-related bugs at compile time.
    A sound type system forces code to be unambiguous about its types, so type-related bugs that might be tricky to find at runtime are revealed at compile time.

  • More readable code.
    Code is easier to read because you can rely on a value actually having the specified type. In sound Dart, types can't lie.

  • More maintainable code.
    With a sound type system, when you change one piece of code, the type system can warn you about the other pieces of code that just broke.

  • Better ahead of time (AOT) compilation.
    While AOT compilation is possible without types, the generated code is much less efficient.

Tips for passing static analysis

#

Most of the rules for static types are easy to understand. Here are some of the less obvious rules:

  • Use sound return types when overriding methods.
  • Use sound parameter types when overriding methods.
  • Don't use a dynamic list as a typed list.

Let's see these rules in detail, with examples that use the following type hierarchy:

a hierarchy of animals where the supertype is Animal and the subtypes are Alligator, Cat, and HoneyBadger. Cat has the subtypes of Lion and MaineCoon

Use sound return types when overriding methods

#

The return type of a method in a subclass must be the same type or a subtype of the return type of the method in the superclass. Consider the getter method in theAnimal class:

dart
classAnimal{voidchase(Animala){...}Animalgetparent=>...}

Theparent getter method returns anAnimal. In theHoneyBadger subclass, you can replace the getter's return type withHoneyBadger (or any other subtype ofAnimal), but an unrelated type is not allowed.

static analysis: successdart
classHoneyBadgerextendsAnimal{@overridevoidchase(Animala){...}@overrideHoneyBadgergetparent=>...}
static analysis: failuredart
classHoneyBadgerextendsAnimal{@overridevoidchase(Animala){...}@overrideRootgetparent=>...}

Use sound parameter types when overriding methods

#

The parameter of an overridden method must have either the same type or a supertype of the corresponding parameter in the superclass. Don't "tighten" the parameter type by replacing the type with a subtype of the original parameter.

Note

Consider thechase(Animal) method for theAnimal class:

dart
classAnimal{voidchase(Animala){...}Animalgetparent=>...}

Thechase() method takes anAnimal. AHoneyBadger chases anything. It's OK to override thechase() method to take anything (Object).

static analysis: successdart
classHoneyBadgerextendsAnimal{@overridevoidchase(Objecta){...}@overrideAnimalgetparent=>...}

The following code tightens the parameter on thechase() method fromAnimal toMouse, a subclass ofAnimal.

static analysis: failuredart
classMouseextendsAnimal{...}classCatextendsAnimal{@overridevoidchase(Mousea){...}}

This code is not type safe because it would then be possible to define a cat and send it after an alligator:

dart
Animala=Cat();a.chase(Alligator());// Not type safe or feline safe.

Don't use a dynamic list as a typed list

#

Adynamic list is good when you want to have a list with different kinds of things in it. However, you can't use adynamic list as a typed list.

This rule also applies to instances of generic types.

The following code creates adynamic list ofDog, and assigns it to a list of typeCat, which generates an error during static analysis.

static analysis: failuredart
voidmain(){List<Cat>foo=<dynamic>[Dog()];// ErrorList<dynamic>bar=<dynamic>[Dog(),Cat()];// OK}

Runtime checks

#

Runtime checks deal with type safety issues that can't be detected at compile time.

For example, the following code throws an exception at runtime because it's an error to cast a list of dogs to a list of cats:

runtime: failuredart
voidmain(){List<Animal>animals=<Dog>[Dog()];List<Cat>cats=animalsasList<Cat>;}

Implicit downcasts fromdynamic

#

Expressions with a static type ofdynamic can be implicitly cast to a more specific type. If the actual type doesn't match, the cast throws an error at run time. Consider the followingassumeString method:

static analysis: successdart
intassumeString(dynamicobject){Stringstring=object;// Check at run time that `object` is a `String`.returnstring.length;}

In this example, ifobject is aString, the cast succeeds. If it's not a subtype ofString, such asint, aTypeError is thrown:

runtime: failuredart
finallength=assumeString(1);
Tip

Type inference

#

The analyzer can infer types for fields, methods, local variables, and most generic type arguments. When the analyzer doesn't have enough information to infer a specific type, it uses thedynamic type.

Here's an example of how type inference works with generics. In this example, a variable namedarguments holds a map that pairs string keys with values of various types.

If you explicitly type the variable, you might write this:

dart
Map<String,Object?>arguments={'argA':'hello','argB':42};

Alternatively, you can usevar orfinal and let Dart infer the type:

dart
vararguments={'argA':'hello','argB':42};// Map<String, Object>

The map literal infers its type from its entries, and then the variable infers its type from the map literal's type. In this map, the keys are both strings, but the values have different types (String andint, which have the upper boundObject). So the map literal has the typeMap<String, Object>, and so does thearguments variable.

Field and method inference

#

A field or method that has no specified type and that overrides a field or method from the superclass, inherits the type of the superclass method or field.

A field that does not have a declared or inherited type but that is declared with an initial value, gets an inferred type based on the initial value.

Static field inference

#

Static fields and variables get their types inferred from their initializer. Note that inference fails if it encounters a cycle (that is, inferring a type for the variable depends on knowing the type of that variable).

Local variable inference

#

Local variable types are inferred from their initializer, if any. Subsequent assignments are not taken into account. This may mean that too precise a type may be inferred. If so, you can add a type annotation.

static analysis: failuredart
varx=3;// x is inferred as an int.x=4.0;
static analysis: successdart
numy=3;// A num can be double or int.y=4.0;

Type argument inference

#

Type arguments to constructor calls andgeneric method invocations are inferred based on a combination of downward information from the context of occurrence, and upward information from the arguments to the constructor or generic method. If inference is not doing what you want or expect, you can always explicitly specify the type arguments.

static analysis: successdart
// Inferred as if you wrote <int>[].List<int>listOfInt=[];// Inferred as if you wrote <double>[3.0].varlistOfDouble=[3.0];// Inferred as Iterable<int>.varints=listOfDouble.map((x)=>x.toInt());

In the last example,x is inferred asdouble using downward information. The return type of the closure is inferred asint using upward information. Dart uses this return type as upward information when inferring themap() method's type argument:<int>.

Inference using bounds

#
Version note

With the inference using bounds feature, Dart's type inference algorithm generates constraints by combining existing constraints with the declared type bounds, not just best-effort approximations.

This is especially important forF-bounded types, where inference using bounds correctly infers that, in the example below,X can be bound toB. Without the feature, the type argument must be specified explicitly:f<B>(C()):

dart
classA<XextendsA<X>>{}classBextendsA<B>{}classCextendsB{}voidf<XextendsA<X>>(Xx){}voidmain(){f(B());// OK.// OK. Without using bounds, inference relying on best-effort approximations// would fail after detecting that `C` is not a subtype of `A<C>`.f(C());f<B>(C());// OK.}

Here's a more realistic example using everyday types in Dart likeint ornum:

dart
Xmax<XextendsComparable<X>>(Xx1,Xx2)=>x1.compareTo(x2)>0?x1:x2;voidmain(){// Inferred as `max<num>(3, 7)` with the feature, fails without it.max(3,7);}

With inference using bounds, Dart candeconstruct type arguments, extracting type information from a generic type parameter's bound. This allows functions likef in the following example to preserve both the specific iterable type (List orSet)and the element type. Before inference using bounds, this wasn't possible without losing type safety or specific type information.

dart
(X,Y)f<XextendsIterable<Y>,Y>(Xx)=>(x,x.first);voidmain(){var(myList,myInt)=f([1]);myInt.whatever;// Compile-time error, `myInt` has type `int`.var(mySet,myString)=f({'Hello!'});mySet.union({});// Works, `mySet` has type `Set<String>`.}

Without inference using bounds,myInt would have the typedynamic. The previous inference algorithm wouldn't catch the incorrect expressionmyInt.whatever at compile time, and would instead throw at run time. Conversely,mySet.union({}) would be a compile-time error without inference using bounds, because the previous algorithm couldn't preserve the information thatmySet is aSet.

For more information on the inference using bounds algorithm, read thedesign document.

Substituting types

#

When you override a method, you are replacing something of one type (in the old method) with something that might have a new type (in the new method). Similarly, when you pass an argument to a function, you are replacing something that has one type (a parameter with a declared type) with something that has another type (the actual argument). When can you replace something that has one type with something that has a subtype or a supertype?

When substituting types, it helps to think in terms ofconsumers andproducers. A consumer absorbs a type and a producer generates a type.

You can replace a consumer's type with a supertype and a producer's type with a subtype.

Let's look at examples of simple type assignment and assignment with generic types.

Simple type assignment

#

When assigning objects to objects, when can you replace a type with a different type? The answer depends on whether the object is a consumer or a producer.

Consider the following type hierarchy:

a hierarchy of animals where the supertype is Animal and the subtypes are Alligator, Cat, and HoneyBadger. Cat has the subtypes of Lion and MaineCoon

Consider the following simple assignment whereCat c is aconsumer andCat() is aproducer:

dart
Catc=Cat();

In a consuming position, it's safe to replace something that consumes a specific type (Cat) with something that consumes anything (Animal), so replacingCat c withAnimal c is allowed, becauseAnimal is a supertype ofCat.

static analysis: successdart
Animalc=Cat();

But replacingCat c withMaineCoon c breaks type safety, because the superclass may provide a type of Cat with different behaviors, such asLion:

static analysis: failuredart
MaineCoonc=Cat();

In a producing position, it's safe to replace something that produces a type (Cat) with a more specific type (MaineCoon). So, the following is allowed:

static analysis: successdart
Catc=MaineCoon();

Generic type assignment

#

Are the rules the same for generic types? Yes. Consider the hierarchy of lists of animals—aList ofCat is a subtype of aList ofAnimal, and a supertype of aList ofMaineCoon:

List<Animal> -> List<Cat> -> List<MaineCoon>

In the following example, you can assign aMaineCoon list tomyCats becauseList<MaineCoon> is a subtype ofList<Cat>:

static analysis: successdart
List<MaineCoon>myMaineCoons=...List<Cat>myCats=myMaineCoons;

What about going in the other direction? Can you assign anAnimal list to aList<Cat>?

static analysis: failuredart
List<Animal>myAnimals=...List<Cat>myCats=myAnimals;

This assignment doesn't pass static analysis because it creates an implicit downcast, which is disallowed from non-dynamic types such asAnimal.

To make this type of code pass static analysis, you can use an explicit cast.

dart
List<Animal>myAnimals=...List<Cat>myCats=myAnimalsasList<Cat>;

An explicit cast might still fail at runtime, though, depending on the actual type of the list being cast (myAnimals).

Methods

#

When overriding a method, the producer and consumer rules still apply. For example:

Animal class showing the chase method as the consumer and the parent getter as the producer

For a consumer (such as thechase(Animal) method), you can replace the parameter type with a supertype. For a producer (such as theparent getter method), you can replace the return type with a subtype.

For more information, seeUse sound return types when overriding methods andUse sound parameter types when overriding methods.

Covariant parameters

#

Some (rarely used) coding patterns rely on tightening a type by overriding a parameter's type with a subtype, which is invalid. In this case, you can use thecovariant keyword to tell the analyzer that you're doing this intentionally. This removes the static error and instead checks for an invalid argument type at runtime.

The following shows how you might usecovariant:

static analysis: successdart
classAnimal{voidchase(Animalx){...}}classMouseextendsAnimal{...}classCatextendsAnimal{@overridevoidchase(covariantMousex){...}}

Although this example shows usingcovariant in the subtype, thecovariant keyword can be placed in either the superclass or the subclass method. Usually the superclass method is the best place to put it. Thecovariant keyword applies to a single parameter and is also supported on setters and fields.

Other resources

#

The following resources have further information on sound Dart:

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Dart 3.11.0. Page last updated on 2025-09-15.View source orreport an issue.


[8]ページ先頭

©2009-2026 Movatter.jp