有些使用接口的场景,不一定要全部用接口来实现,使用联合类型的代码看起来更简洁,清晰
相对于go和rust来说,联合类型给V加分不少,用起来很舒服
语法类似typescript,使用type 和 | 来定义一个联合类型
使用pub关键字,定义公共的联合类型
使用场景
联合类型相对于接口来说,比较适合于一个类型是已知的几种类型的其中一种,已知类型的数量是有限的,固定的,相对封闭的,不需要考虑未知类型的扩展性
//代码位置:vlib/x/json2/decoder.v
pub type Any = string | int | i64 | f32 | f64 | any_int | any_float | bool | Null | []Any | map[string]Any
还有另一个最大的场景就是V编译器自身,使用了联合类型来包含所有的AST抽象语法树的类型,毕竟AST的节点类型也是有限的,固定的,相对封闭的,后续AST的逻辑代码也变得简洁清晰很多
//代码位置:vlib/v/ast/ast.v
pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl | UnionSumTypeDecl
pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral | CTempVar |
CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | EnumVal |
FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral |
Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr |
RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral |
StructInit | Type | TypeOf | UnsafeExpr
pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | SqlStmt |
StructDecl | TypeDecl
而接口更适合用来支持未知类型的扩展性
使用联合类型
- 联合类型作为函数的参数或返回值,也可以作为变量声明,结构体字段
- 使用match语句,进行类型的进一步判断
- 使用match语句,如果需要修改联合类型的值,需要在变量前加mut
- 使用is关键字联合类型具体是哪一种类型
- 使用as关键字将联合类型转换为另一种类型,当然要转换的类型在联合类型定义的类型范围内
获取联合类型的具体类型
联合类型使用内置方法type_name()来返回变量的具体类型
module main
struct Point {
x int
}
type MySumType = Point | f32 | int
fn main() {
// 联合类型使用type_name()
sa := MySumType(32)
sb := MySumType(f32(123.0))
sc := MySumType(Point{
x: 43
})
println(sa.type_name()) // int
println(sb.type_name()) // f32
println(sc.type_name()) // Point
}
type Str = string | ustring
struct Foo {
v int
}
struct Bar {
}
type FooBar = Foo | Bar
fn main() {
s1 := Str('s')
s2 := Str('s')
u1 := Str('s'.ustring())
u2 := Str('s'.ustring())
println( s1 == s1 ) //联合类型判断相等或不等:同类型,同值
println( s1 == s2 )
println( u1 == u2 )
//类型不同,值相同也不等
foo := FooBar( Foo{v: 0} )
bar := FooBar( Bar{v: 0} )
println( foo.v == bar.v )
println( foo != bar )
}
for is类型循环判断
用于联合类型的类型循环判断(感觉没啥用,就是一个语法糖而已)
module main
struct Milk {
mut:
name string
}
struct Eggs {
mut:
name string
}
type Food = Eggs | Milk
fn main() {
mut f := Food(Eggs{'test'})
//不带mut
for f is Eggs {
println(typeof(f).name)
break
}
//等价于
for {
if f is Eggs {
println(typeof(f).name)
break
}
}
//带mut
for mut f is Eggs {
f.name = 'eggs'
println(f.name)
break
}
//等价于
for {
if mut f is Eggs {
f.name = 'eggs'
println(f.name)
break
}
}
as类型转换
联合类型嵌套
联合类型还可以嵌套使用,支持更复杂的场景
struct FnDecl {
pos int
}
struct StructDecl {
pos int
}
struct IfExpr {
pos int
}
struct IntegerLiteral {
val string
}
type Expr = IfExpr | IntegerLiteral
type Stmt = FnDecl | StructDecl
type Node = Expr | Stmt //联合类型嵌套
module main
fn main() {
mut m := Mysumtype{}
m = int(11)
println(m.str())
}
type Mysumtype = int | string
pub fn (mysum Mysumtype) str() string { // 联合类型的方法
return 'from mysumtype'
}
访问公共字段
联合类型可以在方法中访问所有类型的公共字段,并且支持联合类型的多级嵌套
截图中是实际的V编译器中的例子:
stmt.pos是所有Stmt联合类型的公共字段,有了这个特性以后,代码就可以简洁到像截图中的第一个方法那样
联合类型作为结构体字段
结构体字段的类型也可以是联合体类型
struct MyStruct {
x int
}
struct MyStruct2 {
y string
}
type MySumType = MyStruct | MyStruct2
struct Abc {
bar MySumType // bar字段的类型也可以是联合类型
}
fn main() {
x := Abc{
bar: MyStruct{123}
}
if x.bar is MyStruct {
println(x.bar)
}
match x.bar { // match匹配也可以使用
MyStruct { println(x.bar.x) } // 也会自动造型为具体类型
else {}
}