Artis Universe
Artis Universe

Reputation: 163

Why class cannot be initialised in Switch statement in Swift

wondering why Swift switch statement does not allow to instantiate classes like in other languages and how to solve this. Would be glad if anyone can help out on this. In the example i have created a simple Vehicle class and trying to instantiate its sub classes via switch depending on classSetter value. However the final line of print statement cannot print name property of any of the classes if it is instantiated within switch (or seems to any other kind of conditional) statement.

import UIKit

class Vehicle {
    var name: String {return ""}
}

class Car: Vehicle {
    override var name: String {return "Car"}
}

class Bike: Vehicle {
    override var name: String {return "Bike"}
}

var classSetter:Int = 1

switch classSetter {
case 1:
    println("Initializing Car")
    var vehicle = Car()
case 2:
    println("Initialized Bike")
    let vehicle = Bike()
default:
    println("just defaulted")
}

println("Name property from initialization is \(vehicle.name)")

Upvotes: 1

Views: 2036

Answers (3)

Airspeed Velocity
Airspeed Velocity

Reputation: 40955

Your two vehicles are being declared within the switch’s { }, so they only exist in that block (that is their “scope”). They don’t exist outside it, so you can’t refer to them there, hence the error.

The default solution to this (that other answers are giving) is to declare the vehicle as a var outside the switch, but here’s an alternative: wrap the switch in a closure expression and return a value from it. Why do this? Because then you can use let and not var to declare vehicle:

let vehicle: Vehicle = { // start of a closure expression that returns a Vehicle
    switch classSetter {
    case 1:
        println("Initializing Car")
        return Car()
    case 2:
        println("Initialized Bike")
        return Bike()
    default:
        println("just defaulted")
        return Vehicle()
    }
}()  // note the trailing () because you need to _call_ the expression

println("Name property from initialization is \(vehicle.name)")

It would be nice if if and switch were expressions (i.e. evaluated to a result) so you didn’t need this closure, but for now it’s a reasonable workaround.

Note, several of the answers here that use the var approach suggest making vehicle an Optional value (i.e. Vehicle?). This is not necessary – so long as the code is guaranteed to assign vehicle a value before it is used (and the compiler will check this for you), it doesn’t have to be optional. But I still think the closure expression version is a better way.

By the way, you might want to consider using a protocol for Vehicle instead of a base class, since that way you don’t have to give Vehicle a default but invalid implementation for name:

protocol Vehicle {
    var name: String { get }
}

// one of the benefits of this is you could
// make Car and Bike structs if desired
struct Car: Vehicle {
    var name: String {return "Car"}
}

struct Bike: Vehicle {
    var name: String {return "Bike"}
}

Though this would mean you couldn’t have a default return from the switch statement of a Vehicle(). But chances are that would be bad anyway – an optional Vehicle? with nil representing failure might be a better option:

let vehicle: Vehicle? = {
    switch classSetter {
    case 1:
        println("Initializing Car")
        return Car()
    case 2:
        println("Initialized Bike")
        return Bike()
    default:
        println("no valid value")
        return nil
    }
}()

// btw since vehicle is a Vehicle? you need to unwrap the optional somehow,
// one way is with optional chaining (will print (nil) here)
println("Name property from initialization is \(vehicle?.name)")

If you didn’t want this to be a possibility at all, you could consider making the indicator for different kinds of vehicles an enum so it could only be one of a valid set of vehicles:

enum VehicleKind {
    case Bike, Car
}

let classSetter: VehicleKind = .Car

let vehicle: Vehicle = {
    switch classSetter {
    case .Car:
        println("Initializing Car")
        return Car()
    case .Bike:
        println("Initialized Bike")
        return Bike()
    // no need for a default clause now, since
    // those are the only two possibilities
    }
}()

Upvotes: 6

Fogmeister
Fogmeister

Reputation: 77621

Your assumption is incorrect.

The scope of the variable vehicle is inside the switch. You are then trying to access it outside the switch.

You need to create the variable outside the switch. You can still instantiate it inside.

var vehicle: Vehicle?

Switch... {
    Case
        vehicle = Car()
}

println(vehicle)

Writing on my phone so couldn't give full proper code but this will give you the idea.

Upvotes: 1

Phillip Mills
Phillip Mills

Reputation: 31016

What you're doing doesn't work in other languages either. You need to declare the vehicle variable before you enter the switch so that it's in the same scope as your println. After that, you an assign it whatever you need to inside the switch.

Upvotes: 0

Related Questions