原文地址
作者:丹尼斯
抽象
用底层类型
来解决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
.
转载:https://blog.csdn.net/fqbqrr/article/details/113839120