dip1034加底部类型
原文地址
作者:丹尼斯
抽象
用底层类型来解决d类型系统的部分漏洞和限制.
对表示运行时错误/非终止函数有用.由于用例少,已拒绝先前的dip1017.本dip来补充.
内容
背景
纯函数,代表输入->输出,而计算机,还可能永远计算.因而加上个⊥来表示函数计算过程中,返回/赋值失败.如:
bool isPrime(int x);
bool foo(int x) {
return isPrime(x);
}
虽然bool只返回真/假,但还可能未计算完(出错了).用⊥表示无穷循环/崩溃(内存不足)/抛异常等.有了⊥,并没有加底层值或有运行时开销.
不应与空等单元类型混淆,如:
void assertInBounds(int[] arr, int x);
void foo(int[] arr, int x) {
return assertInBounds(arr, x);
}
单元类型仅含1个值,不需要存储.
调用assertInBounds返回2样:{(), ⊥}.
或者()或者⊥表崩溃.
总是崩溃的函数:
auto foo() {
assert(0);
}
仅返回⊥,但编译器推导为空,这是错的.
原理
显示部分示例.在后几节用无中表示⊥.
跨函数分析流
当前,d识别抛后语句,当(1);等无穷语句及断定(0)后代码为死码.因而可省略中语句.
int main() {
while(true) {}
//不必返回,不可达
}
作为语句时没问题,但基于表达式时,函数作为边界,就丢失分析流信息了.
struct Expected(T, E) {
private SumType!(T, E) data;
T value() {
return data.match!(
(T val) => val,
(E err) { throw new Exception(err.to!string); }//推导为空,不能转为T,因而不编译.
);
}
}
不编译,推导第2个函数返回类型为空,而不是T.虽然后端知道不返回,但前端要确保表达式/模板实例为正确实例,因此不编译,但如果搞成⊥,则可编译.
还有个,λ语法如(T val) => val不能用于(E e)处理,因为d只有抛语句,没有抛表达式.
表达式都要求类型,但抛无类型,因而,不能在λ中抛.当有个兜底的⊥类型时,就可以了.
void foo(int function() f) {}
void main() {
foo(() => throw new Exception());
}
其他示例
在猜语句中,可用默认:断定(0),而不能在λ这样表示,同样是没有返回类型.
alias Var = SumType!(int, double, string);
int round(Var v) {
return v.match!(
(int x) => x;
(double x) => cast(int) x;
other => assert(0); //当前不编译
);
}
有了底层类型,可以用std.exception: handle将异常=>错误,从而不用试/抓块.
auto s = "10,20,30"; //好整
auto r = s.splitter(',').map!(a => to!int(a));
auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => assert(0));
也可用于三元操作符?:.
noreturn abort(const(char)[] message);
int fun() {
int x;
//...
return x != 0 ? 1024 / x : abort(0, "不用计算.");
}
空数组字面类型
当前不能隐式转换void[]为其他类型数组.
int[] test() {
return cast(void[]) [1, 2, 3];
}
如下:
错误:不能隐式转换`void[]`至`int[]`
但,void[]的[]字面,却可以
int[] test() {
pragma(msg, typeof([])); // void[]
return []; //无误.
}
这是编译器允许的特例,自定义时就不行:
import std;
struct CastToIntArray {
int[] arr;
void opAssign(T)(T[] other) {
arr = other.map!(x => cast(int) x).array;
}
}
void main() {
CastToIntArray a;
a = [2.1, 3.1, 4.1];
writeln(a); // [2, 3, 4]
a = []; //不能实例化.
writeln(a);
}
如果把[](空数组)看作底层类型,则可转为任意类型的空数组.如用无中[]而不是空[]来实例化赋值操,则map返回如下构:
struct MapResult {
noreturn[] storage;//无中,可返回任意类型
int front() {
return cast(int) storage[0];
}//如果返回为`动`,可静态推导为总是错误
void popFront() {
storage = storage[1..$];
}
bool empty() {
return (storage.length == 0);
}
}
其空的()函数,可折叠常数(优化)为真.
空针类型,空针型
当前typeof(null)是特殊类型.其为指针/类/数组的子类型,但不能当作任意类型指针.
void foo(T)(T* ptr) {}
void main() {
foo(new int); // 好, T = int
static assert(is(typeof(null): int*)); // okay
foo(null); //不能推导
}
目前不能有typeof(*null).因而期望is(typeof(*null) == noreturn)和is(typeof(null) == noreturn*).
不中函数
d有个内部不中函数列表,不中,在优化上有优势.但这样搞,不能扩展.如c的退出().如果有无中,则:
extern(C) noreturn exit();//无中
可同类型系统交流,并允许优化.
标准名
本dip用无中来表明这个特点.与zig/c++一样.
typeof([]) == noreturn[],无中更直接.
一个特例是:
auto x = [];
x ~= 3; //不能从[](空数组)推导类型
noreturn[]也叫typeof([])而noreturn*也叫typeof(null).
先前工作与其他语言
dip1017
rust:!表示never.
ts:never类型.用于抓动态类型错误.
function foo(value: string | number) {
if (typeof value === "string") {
value; // 串
} else if (typeof value === "number") {
value; // 数字
} else {
value; // 错误类型.
}
}
zig:不可达的无中.
switch(value) {
case x => 0;
case y => 1;
case z => unreachable;
else unreachable;
}
d等价版:
switch(value) {
case x: return 0;
case y: return 1;
case z: assert(0);
default: assert(0);
}
d目前不能在基于表达式中用断定(0).zig基于表达式的.
描述
(0)已加底层类型至D
用于表示中止程序的表达式的类型.
null为底层类型指针而[]为底层类型数组.
如下可访问他们:
typeof(*null);
typeof([][0]);
typeof(assert(0));
typeof(throw new Exception()); //依赖(4)
(1)类似串/大小型,隐式为该型添加一个标准别名至每个模块.
该别名为noreturn(无中).
alias noreturn = typeof(*null);
(2)所有可重载内置符号有相应无中版本特化.
如assert(0) + assert(0)导致歧义:是int, long, float 或double版本加?
但,现在是is(typeof(assert(0) + assert(0)) == noreturn).
如[1, 2, 3] ~ assert(0),是连接int还是int[]?
因为noreturn是任意类型的子类,有歧义.但实际不重要,因而结果类型为整[],并编译为:
auto __tmp = [1, 2, 3];
assert(0);
(3)允许隐式从无中转为其他任意类型.
但不能隐式转为noreturn(无中),无中=>任意,但反过来不成立.即is(noreturn : T)为true.重载函数的匹配级为可隐式转换匹配.
定义无中的新协变规则,对T:
/* 1 */ is(noreturn[] : T[])
/* 2 */ is(noreturn* : T*)
/* 3 */ is(noreturn function(S) : T function(S), S...)
/* 4 */ is(noreturn delegate(S) : T delegate(S), S...)
1和2确保可相应隐式转换null和[]为指针/数组.
3和4确保不用转换,直接传递noreturn function() exit给int function() callback参数.即无中函数选择性大.
注意:is(noreturn:T)不成立,因为在元素类型上,指针/数组当前不协变.而函数指针在返回类型上不协变.协变类似kt的协变.
即,虽然class C : Object,但is(C[] : Object[])不成立,就代表不协变.
同样is(dchar*:int*) == false而is(char function() : ubyte function()) == false.
当前语言,可隐式转换typeof(null)为数组/函数指针/闭包/类/接口,因为他们的类型都有null值.
因而,在提出dip后,当typeof(null)变成noreturn*时这二者一样类型,is(typeof(null) : int[])和is(typeof(null) : void function())仍为真.
(4)加抛表达式替换抛语句.
throw(一元抛)与cast()(转换())是同级运算.
一般类似throw new Exception(),但仍可能要消歧,如:
throw E0 + E1 => (throw E0) + E1
throw E0 = E1 => (throw E0) = E1
(5)如每条路径均为死循环或无中类型,则动可推导为无中.
控制流退出函数时按空而不是noreturn(无中),因为,人们习惯了空.
//返回`void`
auto main() {
import std;
writeln("你好");
}
// 返回`noreturn`
auto foo(int x) {
if (x > 0) {
throw new Exception("");
} else if (x < 0) {
while(true) {}
} else {
cast(noreturn) main();
}
}
noreturn(无中)的属性:
noreturn.mangleof == "b";
随意的,只是b是还没用的数字字符.
当前,typeof(null).mangleof == “n”.将改为"pb",底层指针.
不同d版本编译时,可能有问题.
但,在模板外很少用typeof(null)的签名.大小:
noreturn.sizeof == 0;
搞成0,直接点.不折腾.
noreturn.alignof == 0;
对齐为0.强制T*为0,适合typeof(null).
noreturn.init == assert(0);
noreturn(无中)无值,所以无初值(init),因而底层为⊥,出现noreturn.init时,就是assert(0).
noreturn*.sizeof == size_t.sizeof;
noreturn[].sizeof == size_t.sizeof * 2;
尽管noreturn*不必存储,但与其他指针一样大.
与typeof(null).sizeof==size_t.sizeof一致.
与typeof(null)一样,也不需要存储typeof([]).
但T[]应与构一致.
struct slice(T) {
size_t length;
T* ptr;
}
同其他语言特征交互
先前的dip1017用例太少,而现在允许用底部类型,则可在表达式中用断定(0).
声明
带初化的用noreturn(无中)定义变量,在活动时,简单生成式.而不带初化,则访问变量时,生成断定(0),在可能声明未用的无中泛型代码时,有用.一旦在这些例子时,就生成断定(0),然后结束程序.
在全局域初化无中式导致编译错误.
int a = throw new Exception("nope");
无法访问主函数.
类似:
int a = () {throw new Exception(""); return int.init;}();
// 错误: 未抓编译时执行异常
enum也可noreturn,但必须显式初化为底层值,编译器,从0开始赋值.一直到溢出错误.
0长度noreturn静态数组,是单位类型.而[N]则表示N个无中.
允许d的main(主)和外(C)主返回无中.
表达式
二元表达式/参数从左->右求值,如下:
int foo(int x, int y, string z);
int counter;
auto bar() {
return foo(counter = 0, throw new Exception("left"), assert(0, "right"));
}
等价于:
noreturn bar() {
counter = 0;
throw new Exception("left");
}
而||/&&/?:/赋值式仍然一样.
a || assert(0); //a为假,崩溃
a && assert(0); //a为真,崩溃
a ? b : assert(0); //a为假,崩溃
a ? assert(0) : b; //a为真,崩溃
int[] arr;
arr[assert(0, "left")] = assert(0, "right");
//定义断定为左,还是右.
转换操作符
可显式转换任何类型为无中.注意不要与隐式搞混了.它生成断定(0):
int x;
int bar();
auto foo() {
cast(noreturn) (x = bar());
//与{x = bar(); assert(0);}一样.
}
存储类
可用无中加存储类至声明中.
这些声明不生成存储,他们不影响生成代码,但仍受存储类的影响.因而,不能用无中修饰域/不变.
void foo(scope noreturn* ptr) {
static noreturn* gPtr;
immutable noreturn x;
x = assert(0);//编译错误,不能赋值给不变
gPtr = ptr; // 编译错误,转义针函数
}
类
类不能从无中中继承.
尽管无中是对象的子类,但定义类型的子类很难理解,增加复杂性,未来如果从无中继承有意义,则可能会放松限制.
当前,盖方法允许协变返回类型,但不允许用逆变参数类型.即,覆盖返回T的方法时,可用T的子类.而盖参数时,必须用精确匹配类型.
class A {
A foo(A param) {
return param;
}
}
class B {
//允许返回B类型,但参数不行,因为返回可放松,而参数要精确.
override B foo(Object param) {
assert(0);
}
}
添加底层类型不改变这些规则.可用无中盖T,因为无中是所有T的子型.但在参数上,则不行.必须精确匹配.
改变语法
抛语句变为抛式,这样可用在λ上.
NonEmptyStatementNoCaseNoDefault:
- ThrowStatement
- ThrowStatement:
- throw Expression ;
UnaryExpression:
+ ThrowExpression
+ ThrowExpression:
+ throw Expression
限制
尽管由于隐式转换和无名字混杂可用无中声明c函数,c++函数指针的混杂中有中类型.同c++函数对接时,在有[[无中]]时,改中类型为无中不一定有用.因为c++没有该类型,可按空来在外(C++)中模拟无中.这对[[无中]]函数来说很常见.返回类型非空时,可忽略[[无中]]或加包装代码:
extern(C++) int cppExit();
//中 整
void main() {
writeln("hello world");
cppExit();//不中,但无所谓
}
//可用dExit,其与类型系统交流不错
noreturn dExit() {
cast(noreturn) cppExit();
}
//负载可加在调用者上
auto f = () => cast(noreturn) cppExit();
替代方案
用@无中属性.类似:
version (LDC)
enum noreturn = ldc.attributes.llvmAttr("noreturn"));
else version (GDC)
enum noreturn = gcc.attribute.attribute("noreturn");
else version (DigitalMars)
enum noreturn = pragma(noreturn);
//示例
// 用法:
@noreturn void exit();
然而,不能解决[]和null,及λ中抛异常的问题,及将负担交给了程序员,而不是类型系统.
//无中类型
auto copyFunc(alias func, T...)(T params) {
return func(params);
}
//无中属性
template copyFunc(alias func) {
import std.traits: hasUDA;
static if (hasUDA!(func, noreturn)) {
@noreturn auto copyFunc(T...)(T params) {
return func(params);
}
} else {
auto copyFunc(T...)(T params) {
return func(params);
}
}
}
破坏改变及过时
会破坏使用typeof([])的声明或模板函数.
也可能破坏用[]推导变量类型的代码.也可能与使用无中符号的模块冲突.
正式评估
d作者迅速通过该dip,并认为优于dip1017.
浙公网安备 33010602011771号