联合类型(sum types)
定义联合类型
V的联合类型在V编译器代码中大量采用
有些使用接口的场景,不一定要全部用接口来实现,使用联合类型的代码看起来更简洁,清晰
相对于go和rust来说,联合类型给V加分不少,用起来很舒服
语法类似typescript,使用type 和 | 来定义一个联合类型
1. //定义联合类型,表示类型Expr可以是这几种类型的其中一种
2. type Expr = Foo | BoolExpr | BinExpr | UnaryExpr
使用pub关键字,定义公共的联合类型
1. pub type Expr = Foo | BoolExpr | BinExpr | UnaryExpr
使用场景
联合类型相对于接口来说,比较适合于一个类型是已知的几种类型的其中一种,已知类型的数量是有限的,固定的,相对封闭的,不需要考虑未知类型的扩展性
比如,x.json2模块中,使用联合类型来包含所有json的节点类型,json节点类型种类是相对固定的,使用了联合类型这种数据结构来表示,后续的代码就变得很简洁清晰
1. //代码位置:vlib/x/json2/decoder.v
2. pub type Any = string | int | i64 | f32 | f64 | any_int | any_float | bool | Null | []Any | map[string]Any
还有另一个最大的场景就是V编译器自身,使用了联合类型来包含所有的AST抽象语法树的类型,毕竟AST的节点类型也是有限的,固定的,相对封闭的,后续AST的逻辑代码也变得简洁清晰很多
1. //代码位置:vlib/v/ast/ast.v
2. pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl | UnionSumTypeDecl
4. pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral | CTempVar |
5. CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | EnumVal |
6. FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral |
7. Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr |
8. RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral |
9. StructInit | Type | TypeOf | UnsafeExpr
11. pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
12. EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
13. GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | SqlStmt |
14. StructDecl | TypeDecl
而接口更适合用来支持未知类型的扩展性
使用联合类型
- 联合类型作为函数的参数或返回值,也可以作为变量声明,结构体字段
- 使用match语句,进行类型的进一步判断
- 使用match语句,如果需要修改联合类型的值,需要在变量前加mut
- 使用is关键字联合类型具体是哪一种类型
- 使用as关键字将联合类型转换为另一种类型,当然要转换的类型在联合类型定义的类型范围内
1. module main
3. struct User {
4. name string
5. age int
6. }
8. fn (m &User) str() string {
9. return 'name:$m.name,age:$m.age'
10. }
12. // 联合类型声明
13. type MySum = User | int | string
15. fn (ms MySum) str() string {
16. if ms is int { // 使用is关键字,判断联合类型具体是哪种类型
17. println('ms type is int')
18. }
19. match ms { // 对接收到的联合类型,使用match语句进行类型判断,每个match分支的ms变量都会被自动造型为分支中对应的类型
20. int { return ms.str() }
21. string { return ms }
22. User { return ms.str() }
23. }
24. }
26. fn add(ms MySum) { // 联合类型作为参数
27. match ms { // 可以对接收到的联合类型,使用match语句进行类型判断,每个match分支的ms变量都会被自动造型为分支中对应的类型
28. int { println('ms is int,value is $ms') }
29. string { println('ms is string,value is $ms') }
30. User { println('ms is User,value is $ms.str()') }
31. }
32. }
34. fn sub(i int, s string, u User) MySum { // 联合类型作为返回值
35. return i
36. // return s //这个也可以
37. // return User{name:'tom',age:3} //这个也可以
38. }
40. fn main() {
41. i := 123
42. s := 'abc'
43. u := User{
44. name: 'tom'
45. age: 33
46. }
47. mut res := MySum{} // 声明联合类型变量
48. res = i
49. println(res) // 输出123
50. res = s
51. println(res) // 输出abc
52. res = u
53. println(res) // 输出name:tom,age:33
54. match res { // 判断具体类型
55. int { println('res is:$res.str()') }
56. string { println('res is:$res') }
57. User { println('res is:$res.str()') }
58. }
59. match mut res { // 如果需要在分支中修改res,需要加mut
60. int {
61. res = MySum(3)
62. println('new res value is: $res')
63. }
64. string {
65. res = 'abc'
66. println('new res value is: $res')
67. }
68. User {
69. res = User{
70. name: 'jack'
71. age: 12
72. }
73. println('new res value is:$res.str()')
74. }
75. }
76. user := res as User // 也可以通过as,进行显示造型
77. println(user.name)
78. add(i)
79. add(s)
80. }
获取联合类型的具体类型
联合类型使用内置方法type_name()来返回变量的具体类型
1. module main
3. struct Point {
4. x int
5. }
7. type MySumType = Point | f32 | int
9. fn main() {
10. // 联合类型使用type_name()
11. sa := MySumType(32)
12. sb := MySumType(f32(123.0))
13. sc := MySumType(Point{
14. x: 43
15. })
16. println(sa.type_name()) // int
17. println(sb.type_name()) // f32
18. println(sc.type_name()) // Point
19. }
联合类型相等判断
1. type Str = string | ustring
3. struct Foo {
4. v int
5. }
7. struct Bar {
8. v int
9. }
11. type FooBar = Foo | Bar
13. fn main() {
14. s1 := Str('s')
15. s2 := Str('s')
16. u1 := Str('s'.ustring())
17. u2 := Str('s'.ustring())
18. println( s1 == s1 ) //联合类型判断相等或不等:同类型,同值
19. println( s1 == s2 )
20. println( u1 == u1 )
21. println( u1 == u2 )
23. //类型不同,值相同也不等
24. foo := FooBar( Foo{v: 0} )
25. bar := FooBar( Bar{v: 0} )
26. println( foo.v == bar.v )
27. println( foo != bar )
28. }
for is类型循环判断
用于联合类型的类型循环判断(感觉没啥用,就是一个语法糖而已)
1. module main
3. struct Milk {
4. mut:
5. name string
6. }
8. struct Eggs {
9. mut:
10. name string
11. }
13. type Food = Eggs | Milk
15. fn main() {
16. mut f := Food(Eggs{'test'})
17. //不带mut
18. for f is Eggs {
19. println(typeof(f).name)
20. break
21. }
22. //等价于
23. for {
24. if f is Eggs {
25. println(typeof(f).name)
26. break
27. }
28. }
29. //带mut
30. for mut f is Eggs {
31. f.name = 'eggs'
32. println(f.name)
33. break
34. }
35. //等价于
36. for {
37. if mut f is Eggs {
38. f.name = 'eggs'
39. println(f.name)
40. break
41. }
42. }
43. }
as类型转换
1. module main
3. type Mysumtype = bool | f64 | int | string
5. fn main() {
6. x := Mysumtype(3)
7. x2 := x as int
8. println(x2)
9. }
联合类型嵌套
联合类型还可以嵌套使用,支持更复杂的场景
1. struct FnDecl {
2. pos int
3. }
5. struct StructDecl {
6. pos int
7. }
10. struct IfExpr {
11. pos int
12. }
14. struct IntegerLiteral {
15. val string
16. }
18. type Expr = IfExpr | IntegerLiteral
19. type Stmt = FnDecl | StructDecl
20. type Node = Expr | Stmt //联合类型嵌套
联合类型方法
可以像结构体那样,给联合类型添加方法
1. module main
3. fn main() {
4. mut m := Mysumtype{}
5. m = int(11)
6. println(m.str())
7. }
9. type Mysumtype = int | string
11. pub fn (mysum Mysumtype) str() string { // 联合类型的方法
12. return 'from mysumtype'
13. }
访问公共字段
联合类型可以在方法中访问所有类型的公共字段,并且支持联合类型的多级嵌套
截图中是实际的V编译器中的例子:
stmt.pos是所有Stmt联合类型的公共字段,有了这个特性以后,代码就可以简洁到像截图中的第一个方法那样

联合类型作为结构体字段
结构体字段的类型也可以是联合体类型
1. struct MyStruct {
2. x int
3. }
5. struct MyStruct2 {
6. y string
7. }
9. type MySumType = MyStruct | MyStruct2
11. struct Abc {
12. bar MySumType // bar字段的类型也可以是联合类型
13. }
15. fn main() {
16. x := Abc{
17. bar: MyStruct{123}
18. }
19. if x.bar is MyStruct {
20. println(x.bar)
21. }
22. match x.bar { // match匹配也可以使用
23. MyStruct { println(x.bar.x) } // 也会自动造型为具体类型
24. else {}
25. }
26. }
子类不允许是指针类型
使用联合类型时,有一点需要注意:联合类型的子类中不允许出现指针类型,编译器会检查并报错
1. struct Abc {
2. val string
3. }
5. struct Xyz {
6. foo string
7. }
9. type Alphabet1 = Abc | string | &Xyz //不允许指针类型
