类型系统可以自动验证某些方面的正确性。例如,Rust的类型系统可以证明引用不会超过引用对象的寿命,或者引用对象没有被另一个线程修改。
但是类型系统非常受限制:
他们很快遇到了可判定性问题。特别是,类型系统本身应该是可确定的,但是许多实用的类型系统是偶然图灵完整的(包括C ++(由于模板)和Rust(由于特征))。同样,在通常情况下,它们正在验证的程序的某些属性可能不确定,最著名的是某些程序是否停止(或发散)。
此外,类型系统应能快速运行,最好在线性时间内运行。并非所有可能的证明都应在类型系统中标出。例如,通常避免整个程序的分析,并且证明的范围仅限于单个模块或功能。
由于这些限制,类型系统倾向于仅验证容易证明的相当弱的属性,例如,使用正确类型的值调用了函数。即便如此,它仍然严重限制了表达能力,因此通常有变通方法(例如interface{}
Go,dynamic
C#,Object
Java,void*
C)或什至使用完全避开静态类型的语言。
我们验证的属性越强,该语言通常所具有的表达能力就越差。如果您已经编写了Rust,那么您将知道在这些“与编译器抗争”的时刻,编译器会拒绝看似正确的代码,因为它无法证明正确性。在某些情况下,即使我们相信可以证明其正确性,也无法在Rust中表达某个程序。unsafe
Rust或C#中的机制允许您摆脱类型系统的限制。在某些情况下,将检查推迟到运行时可能是另一种选择–但这意味着我们不能拒绝某些无效的程序。这是一个定义问题。就类型系统而言,能够紧急恐慌的Rust程序是安全的,但不一定从程序员或用户的角度来看。
语言是与它们的类型系统一起设计的。很少有将新类型的系统强加于现有的语言上(例如,请参见MyPy,Flow或TypeScript)。该语言将尝试使编写符合类型系统的代码变得容易,例如通过提供类型注释或引入易于证明的控制流结构。不同的语言可能会得到不同的解决方案。例如,Java具有final
只分配一次的变量的概念,类似于Rust的非mut
变量:
final int x;
if (...) { ... }
else { ... }
doSomethingWith(x);
Java具有类型系统规则,以确定在访问变量之前,所有路径是分配变量还是终止函数。相反,Rust通过没有声明但未设计的变量来简化此证明,但允许您从控制流语句返回值:
let x = if ... { ... } else { ... };
do_something_with(x)
在确定作业时,这似乎只是一个很小的问题,但是明确的范围对于与寿命相关的证明极为重要。
如果要在Java上应用Rust风格的类型系统,那么我们将面临的问题要大得多:Java对象没有使用生命周期进行注释,因此我们必须将其视为&'static SomeClass
或Arc<dyn SomeClass>
。那会削弱任何由此产生的证据。Java也没有类型级别的不变性概念,因此我们无法区分&
和&mut
类型。我们将必须将任何对象都视为Cell或Mutex,尽管这可能会提供比Java实际提供的更强的保证(更改Java字段是线程安全的,除非同步且易变)。最后,Rust没有Java样式实现继承的概念。
TL; DR:类型系统是定理证明。但是它们受到可决定性问题和性能问题的限制。您不能简单地采用一种类型系统并将其应用于另一种语言,因为目标的语言语法可能无法提供必要的信息,并且因为语义可能不兼容。