lnshi
lnshi

Reputation: 2898

Go: what is wrong with this multiple case conditions in type switch assertions?

I have the code generally like this:

func BulkInsert(docs interface{}) {
    switch data := docs.(type) {
        case map[string] *model.SnapshotByConv, map[string] *model.UserSnapshotsMap:
            for ver, _ := range data {
                // other logics ...
            }
        case map[int64] map[string] *model.Delta:
            for ver, _ := range data {
                // other logics ...
            }
    }
}

Then when compile I got the error: cannot range over data (type interface {}), it is raised by the first range.

If I remove the multiple types in the first case, which means leave it as case map[string] *model.SnapshotByConv:

Then the compile error is gone, it is wried, I need to proceed the exactly same logics on those two types, so why I cannot put them in a same case?

Pls help, thanks.

I think the case here is not same with the one already had answer here: golang multiple case in type switch, which is trying to find a way to identify the types, but here is I just don't want to identify certain types and simply run some logics on it, I cannot find a elegant way to do this.

Upvotes: 1

Views: 5033

Answers (1)

Josef Grahn
Josef Grahn

Reputation: 1635

If the case enumerates multiple types (like it does in your example), the variable's type won't be narrowed, so in your case it will be interface{}. This is what the question you link to says, and there is no way around it.

To dispatch some generic code for two different type cases, you will need to introduce an interface somewhere inbetween. (This might change if Go at some point introduces generics, but from the looks of it, that won't happen soon if ever.) There are a few ways you could do this, here are three:

1 - Define types for your maps, and add a common interface

Don't use the maps directly, but define types that are maps, and add functions to them.

type SnapshotMap map[string]*model.SnapshotByConv
type UserSnapshotMap map[string]*model.UserSnapshotsMap

func (sm *SnapshotMap) DoLogic() {/*...*/}
func (usm *UserSnapshotMap) DoLogic() {/*...*/}

type LogicDoer interface {
    DoLogic()
}

// ...

switch data := docs.(type) {
case LogicDoer:
    data.DoLogic()
// ...
}

2 - Make the maps hold a common interface

Don't create maps of pointers, but maps of interfaces that your snapshot types share.

type Snapshot interface {
    LogicCommonToSnapshots()
}
var doc interface{}

// Somewhere else, this happens rather than a creating map[string]*Something
doc = make(map[string]Snapshot)

// ...

switch data := doc.(type) {
case map[string]Snapshot:
    for ver, _ := range data {
        ver.LogicCommonToSnapshots()
    }
// ...
}

3 - Break out the logic and make it call back onto a common interface

Split the switch case into two separate cases, but break out the logic into a function that can operate on either snapshot type. That way, you won't have to duplicate the logic.

type Snapshot interface {
    LogicCommonToSnapshots()
}

func ComplexLogic(s Snapshot) {/*...*/}

switch data := doc.(type) {
case map[string] *model.SnapshotByConv:
    for ver, _ := range data
        ComplexLogic(ver)
    }
case map[string] *model.UserSnapshotsMap:
    for ver, _ := range data {
        ComplexLogic(ver)
    }
// ...
}

Notice that in all three cases, there is an interface that separates the common logic from the object that logic acts on. You must necessarily introduce a dynamic binding between the logic and the object to accomplish what you describe, either through interfaces or by e.g. creating an anonymous function that captures the object gets passed to the logic.

Upvotes: 3

Related Questions