编程语言的世界里,“空”是永远的问题,它永远在,又没有一个明确的归属。
如果“空”是单独定义的一类存在类型,那么任意类型都会有属于“空”的情况,但一个纯粹的“空”存在时,除非有其他足够的信息,否则“空”在此时是任意归属的,此时你可以非常容易的创造谬误逻辑。
如果“空”是无法单独存在的,只有那些能够空的类型会存在自己的类型为空的情况,即:“空”是带类型的。此时,任意一个“空”都有类型。存在任意一个“空”,我们都可以知道它的类型归属,写出谬误逻辑的可能减小了。
对于“空”的操作是不允许的。所以,golang提供了基础类型的默认值,这是方便了程序员,但也出现了一个问题,“空”是不存在,而基础类型的默认值一定是非空的,当其他角色获取数据时,不知道是默认值还是空。现实意义中,“空”一定是和默认值不等的,某些少数情况下二者可以互相替换。
golang既然为基础类型提供了默认值,那就确保了对基础类型一定是可以进行操作的;同时golang也提供了引用类型,引用类型也是同样的逻辑,尽量保证对空的操作是不出错的。对于slice、map等类型是类型明确的,某些方法调用时确实是可以这么操作;对于接口类型,为“空”时,任何方法调用都是不可能的。
golang的接口是一种约束,约定有什么功能,能提供什么方法。
存在类型A,存在接口类型C,当某个A赋值给C时,C不为空,C为空的情况有且仅当定义C且不赋值的情况。
golang没有要求强制处理为空的情况,它默认尽力保证为空时也可运行,导致“类型B的空”向下传递到类型C,“类型C的空”明确定义为“C未赋值的情况”,所以空B赋值给C是“非空的C”,空的陷阱在不同类型赋值时,就埋下了.
一切事物分为“本质”与“形式”。golang中同样可以这么理解。
基础类型,string,int32,bool等属于“本质”。
组合类型,struct,slice[],map,chan,当他们由基础类型构成时,属于“本质”。
接口类型,我们显然注意到,它完全是形式上的约束,属于“形式”。
无论是现实世界,还是代码世界,能交互的只有“本质”。所以golang的接口光定义,未赋值时,它时纯粹的“形式”,(type,value)都是nil。
定义一个int[],无论赋值与否,都是属于“本质”,赋值(或make)了,显然属于本质;如果光定义未赋值时,在golang的定义中,也属于本质,它是,类型为int[],值为nil的存在,根据golang的哲学,此时它可以进行一些操作,如len,cap等而不报错。
当golang的接口赋值时,赋值给接口的如果是带了“本质”的存在,那么该接口是非空的了;如果赋值给接口的是完全的“形式”(即空接口的等价含义;同样即(type,value)=(nil,nil)),那么该接口还是空的,即(type,value)=(nil,nil)。
当golang中的组合类型使用了接口时,在赋值该组合类型时,如果没有使用接口,那就什么也不做,如果用了接口,那么会要求接口存在一个“本质”,毕竟,未赋值“本质”的接口是“空”的同义词,即(type,value)=(nil,nil),纯粹的形式无法进行交互,任何有意义的操作都是无效、报错的。
这样看来,golang中,空接口(接口定义不重要,只要缺失了“本质”) = 纯粹的空(类似java的null)。
在形式上:空接口是golang中所有类型的父类型(忽略接口定义中的形式约束)。
在严谨的形式上:empty的空接口(即golang的any),确实是golang所有类型的父类型。
golang中,所有存在“本质”的类型,无论任何操作,不可能出现缺失“本质”的存在。只有你定义了一个接口(或any),在使用该接口时,会出现“本质”缺失,无法进行任何操作。
在Java中,null是任意类型的子类型,是“形式”的“空”,也是“本质”的空,归根结底是“本质”上的空,形式是本质的映射,所有也有形式上的空,是一个底层类型。object是Java的顶层类型。
golang禁止了“本质”层面上的空,留下了纯粹“形式”上的空,然后纯粹“形式”上的空就变成了一个顶层类型。 然而,“空”是现实世界真实存在的,代码世界也是必然存在的。golang也逃不过“空”的自然产生与表达。golang做了一些操作,基本类型的空变成了“默认值”,其他有“本质”的类型(引用类型)的“空”变成了有type,无value的存在形式。
孰优孰劣,我不知道。就像在现实世界,a的空与b的空,有区别吗,谁知道呢?
在rust语言中,“空”的类型是与Java类似的,存在本质上的“空”,Option
golang的缺点在于,“空”是必然存在的,却缺失了表达的能力,混淆了“空”与“默认值”。