引言
先看一段代码:
dart
void main() {
List a = [1, 2, 3];
}
你认为,此段代码中的变量a
在运行时是什么类型的?
strict-raw-types
dart静态分析器有一个选项叫做strict-raw-types。先看看什么是strict-raw-types
,也就是严格原始类型
:
本文件规定了通过静态分析选项启用的"严格原始类型"模式。作为一项静态分析选项,我们仅打算在Dart分析器中实现此功能。在此功能下,省略类型参数的类型被定义为"原始类型"。Dart会使用类型参数的边界来填充此类类型参数;若无边界,则使用
dynamic
类型填充。
以上是机翻,英语好的可以看看原文:
This document specifies the "Strict raw types" mode enabled with a static analysis option. As a static analysis option, we only intend to implement this feature in the Dart Analyzer. Under this feature, a type with omitted type argument(s) is defined as a "raw type." Dart fills in such type arguments with their bounds, or dynamic if there are no bounds.
在实际项目中,我也尝试去理解过,以此来确保我的代码严格遵守dart的类型系统。相信很多人也在网上学习过dart中的dynamic
和Object
的区别,比如说:
Object
是一个类,是所有类的基类,它提供了一些通用方法。需要显式调用方法来操作。dynamic
是一种特殊类型,用于表示动态类型,可以在运行时具有不同的类型值。- 使用
Object
通常是为了访问通用方法,而使用dynamic
通常是为了处理动态的、不确定类型的情况。
其实dynamic
和 Object
之间的微妙差异在`Dart 语言规范中有所体现,以下是最新的 Dart 语言规范第 20.7 节
dynamic
和 Object?
之间存在一个微妙差异。根据最新的Dart 语言规范第 20.7 节:
dynamic
类型是一种静态类型,和Object
一样,它是所有其他类型的超类型。但它与其他类型的不同之处在于,静态分析会假定对其进行的每次成员访问,都存在一个具有能允许该访问的签名的对应成员。
同样的是机翻,说人话就是,dynamic
其实和Object
一样都是其他类型的超类,只是dynamic
类型的变量在静态分析时,调用什么方法或者属性都可以,静态分析器都会假设该成员存在。看代码: void main(List args) { final d = []; final o = <Object?>[];
d.add(Object()); o.add(Object());
d[0].missingMethod(); // 会导致运行时的NoSuchMethodError. // NoSuchMethodError: Class 'Object' has no instance method 'missingMethod'.
o[0].missingMethod(); // 静态分析器直接报错.
}
以下是原文:
The type dynamic is a static type which is a supertype of all other types, just like Object, but it differs from other types in that the static analysis assumes that every member access has a corresponding member with a signature that admits the given access.
所以,dynmaic
实际上是静态类型系统的"逃逸口",dynamic
允许开发者在编译期绕过类型检查,但必须在运行时承担类型不匹配的风险。过度使用会导致代码失去静态类型系统的保护。这也是为什么会有strict-raw-types
这条原则的原因。
回到问题
回到文章开头的问题,我们也可以在规范中找到明确的答案:
If no static type annotation has been provided, the type system considers declarations to have type dynamic. If a generic type is used but type arguments are not provided, the type arguments default to type dynamic.This means that given a generic declaration G<P1, . . . , Pn>. . ., where Pi is a formal type parameter declaration, i ∈ 1..n, the type G is equivalent to G<dynamic, . . . , dynamic>.
翻译过来就是:
如果没有提供静态类型注解,类型系统会认为这些声明的类型为 dynamic。如果使用了泛型类型但没有提供类型参数,那么类型参数默认是 dynamic 类型。这意味着,对于一个泛型声明 G<P1, ..., Pn> ...,其中 Pi 是形式类型参数声明,i 的取值范围是从 1 到 n,那么类型 G 等同于 G<dynamic, ..., dynamic>。
所以回头再看这段代码:
dart
void main() {
List a = [1, 2, 3];
}
开发者常常认为类型推断是从赋值语句的右侧来确定变量a
的类型。看起来变量a
似乎是List<int>
类型。但实际上,Dart会用 dynamic(或者对应类型参数的边界类型)来填充省略的类型参数,比如 List 中的 E。List a; 纯粹是 List<dynamic> a
; 的简写形式。然后,类型推断是从变量a
流向赋值语句右侧的表达式。这在另一个例子中会更加明显:
dart
void main() {
List a = [1, 2, 3]..forEach((e) => print(e.length));
var b = [4, 5, 6]..forEach((e) => print(e.length));
}
第一条语句不会导致任何静态分析错误,因为列表的类型会被推断为List<dynamic>
。相反,当对int
类型调用length
getter时,代码会在运行时抛出no-such-method。
然而,第二条语句允许列表的类型从其元素推断为List<int>
,这会导致静态分析类型错误,提示int
上未定义length
getter。
原始类型(Raw types)还可能导致意外的动态派发(dynamic dispatch):
dart
void main() {
List a = [1, 2, 3]; // 原始类型,等价于 List<dynamic>
a.forEach((e) => print(e.isEven)); // 回调参数 e 的类型是 dynamic
}
开发者可能没有意识到,回调函数的参数e
是dynamic
类型,对isEven
的调用属于动态派发(运行时才会检查方法是否存在)。
启用严格原始类型检查 旨在鼓励开发者填充省略的类型参数,避免默认使用dynamic
。当唯一合适的类型确实是dynamic
时,显式将其作为类型参数(如List<dynamic>
)可以避免原始类型,同时让代码中的动态行为更加明确。
结束语
通过以上阅读,我们对strict-raw-types
的初衷也有一定的了解,也知道了为什么会有这条原则,希望对日后的开发工作有所帮助。最后,附上如何开记strict-raw-types
:
yaml
analyzer:
language:
strict-raw-types: true
也希望大家关注我的微信公众号:OpenFlutter
,感恩。