Russell Gordon

Handling Incorrect User Input

18 October, 2020

    Introduction

    Depending on how far you've made it through the Swift Fundamentals tutorials, when you tried out The Cell Sell incomplete puzzle you might have wondered exactly what the exclamation marks at the end of line 20 were doing.

    What's happening here is the program is collecting input from the user, and attempting to convert it into an integer. This works just fine – as long as the user gives us valid input!

    For example, when the number of daytime minutes equals 50: everything works and we continue on to the number of evening minutes prompt.

    What happens if we stop the program, run it again, and this time, make a mistake and type in the word fifty?

    Now that cannot be converted into an integer, and in this case, the program crashes because the attempt to convert the string provided by readLine() does not succeed.

    NOTE: To be clear, the reason the program crashes is because the ! indicates that we are force-unwrapping an optional result. When we attempt to convert a string to an integer, a nil value is returned if the conversion fails. By force-unwrapping the optional, we are telling the computer that to "trust us" – that the conversion will always work. Clearly that is not always the case, as shown in this example. When a Swift program tries to unwrap an optional value that is nil, the program will crash.

    So how do we handle this?

    Guarding Against Incorrect Input

    Well, I'm going to stop the program.

    I'm going to replace all of this code:

    // Get daytime minutes
    print("Number of daytime minutes? ")
    let day = Int(readLine())!
    

    ...with a somewhat longer section of code that I will explain:

    // Get daytime minutes
    var day = 0
    while true {
        
        // Prompt the user
        print("Number of daytime minutes? ")
        
        // Collect input
        guard let givenInput = readLine() else {
            continue
        }
        
        // Convert to an integer
        guard let givenInteger = Int(givenInput) else {
            continue
        }
        
        // Check that the value is positive
        guard givenInteger > 0 else {
            continue
        }
        
        // Assign the given integer to our "day" variable
        day = givenInteger
        
        // Stop the loop
        break
        
    }
    

    Now that is significantly longer so let's take a look at this.

    Using the debugger to slow things down I'm going to put a breakpoint on line 23, just before the program prompts the user for the number of daytime minutes.

    Program Flow With Correct Input

    So if we run the program – first, let's actually enter valid input.

    The program is now waiting to get the number of daytime minutes.

    I'm going to type 5 which is valid input.

    Let's take a look at what happened. Line 26 occurred, and the readLine() function collects an optional string from the user.

    It's optional because the user might not provide input which would make the input nil which is a problem – but in this case, I did type the characters 5 and then 0.

    So, this guard let statement succeeded. The readLine() optional string was unwrapped to create a regular string.

    Now we're on line 31. We have another guard let statement. In this case, we're taking the given input string and attempting to convert it into an integer.

    The Swift compiler is smart enough to be able to convert a string with the characters 50 to an integer that's actually stored internally as the number 50. So, this statement succeeds.

    NOTE: On a 64-bit processor architecture, for example, the Intel Core i5, a variable of type Int in Swift can hold integer values between -9223372036854775808 and 9223372036854775807. So, internally, a Swift program stores the decimal number 50 as the binary number as 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0011 0010! More about binary numbers here – take a look!

    On this check with a guard statement we're making sure the given integer is more than zero. Of course, 50 is more than zero so this statement succeeds.

    Finally, we know that the value provided by the user is what we want, so we take this given integer and assign it to the day variable that was created outside the scope of the while loop.

    NOTE: Copying the value to the day variable is required so that we can continue to use the value once the loop stops running. The givenInteger variable, created inside the while loop, is destroyed once the loop stops running.

    Then, we break the loop and then we continue and ask for the number of evening minutes.

    So... that's how it works if we get correct input from the user.

    Program Flow With Incorrect Input

    What's more interesting to see is what happens if we don't get correct input from the user.

    So, I'm going to stop the program and run it again.

    We're going to prompt the user for the number of daytime minutes.

    And this time, we'll enter f i f t y.

    That is not valid input.

    The first guard let statement succeeds because the input f i f t y is at least something so the optional string that the readLine() function returns is successfully unwrapped into a regular string which is guaranteed to have a value.

    So in this case we know the value is fifty and on line 31 we're now going to take this string fifty and attempt to make it into an integer.

    Now, a human being knows that the word fifty is equivalent to the numeric value 50 but a computer is not smart enough to make that conversion.

    So, this will fail and the flow of the program falls into the else branch of the guard let statement and hits the continue statement.

    What this does is cause the control flow of the program to jump back up to line 20 and the user is prompted again.

    In other words, the computer is basically saying: "What was that? Try again."

    OK, so this time I'm going to type in -50.

    Let's see what happens.

    The first guard let statement succeeds. -50 is some input so now we have a string that contains the characters - and 5 and 0.

    On this guard let statement it going to attempt to convert that string into a numeric value – an integer.

    After that line runs, the conversion suceeded, and now givenInteger contains the actual numeric value of -50.

    In this next check, line 36, we want to make sure that the provided value is more than zero because it doesn't make sense to collect -50 as the number of daytime minutes that a cell phone user consumed.

    This check fails. -50 is not more than zero. So, again, it falls into the else branch of this guard statement and we hit the continue statement which causes the control flow of the program to jump back up to the top of that loop.

    The user is prompted again.

    Recap

    This time I'll enter a valid value.

    The first guard let statement succeeds. The characters 50 are indeed some kind of input.

    The second guard let statement succeeds because the string 50 can be converted into the numeric value 50.

    This guard let statement succeeds because 50 is more than zero, at which point we can take that input of 50 and assign it to the variable that was created outside the loop.

    Then, we stop the loop.

    So... this pattern of using a while loop to continually prompt the user is extremely common when we accept user input in a command-line program.

    We create a variable called day outside the scope of the while loop so that we can use the variable after that loop concludes – if we just created day inside the while loop then when the loop was done, we wouldn't be able to use it any more.

    That really is the last and most important part of this logic – the value that we now know is correct is assigned to the variable day that's created outside the loop.

    Also important – that we stop the loop by hitting the break statement and then we fall out of the loop and we continue on with our program.

    And so... this is an overview of how to collect user input and to make sure that input is in fact something that's usable and correct for the program we are writing.