NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: Day 8 - Checkpoint 4 Function return question

Forums > 100 Days of SwiftUI

Had some difficulty doing Checkpoint 4, ended up with a solution but didn't quite understand everything that happened, specially with the global return error, so wanted to clarify that. Here's my code:

enum FunctionError: Error {
  case outOfBounds, noRoot
}

func getSqrt(_ num: Int) throws -> Int{
for i in 1...num{
  if num < 1 || num > 10_000{
  throw FunctionError.outOfBounds
  }

  if num == i * i {
  return i
  }
}
throw FunctionError.noRoot
}

let userNum = 25

do {
let result = try getSqrt(userNum)
print("The square root of \(userNum) is \(result)")
} catch FunctionError.outOfBounds{
print("Your number is out of bounds, pick another one.")
} catch FunctionError.noRoot{
print("We couldn't find the root...")
} catch {
print("An error has occurred...")
}

My question is related to the function creation. First i had an else statement after the second if with the throw FunctionError.noRoot but there was an error saying that a return of type Int was missing, that's why i put it outside like i have now. I understand that if I'm saying that the function returns an Int, Swift needs to cover all cases, specially with those 2 if statements, but i thought i covered all the cases using the else at the end, to capture all the other possible situations... Why it doesn't work if i put the throw error inside an else but it does work if i put it outside? Isn't it the same?

There's a user with a similar problem:

https://www.hackingwithswift.com/forums/100-days-of-swiftui/checkpoint-4/15586

But here i wanted to understand better the difference of having a return inside an if statement and outside. If i have my result returned inside an if statement, swift will give me an error, is the best procedure to repeat the same return oustide the if statement?

   

Renato wonders why his code doesn't cover all the cases...

Why it doesn't it work if i put the throw error inside an else but it does work if i put it outside? Isn't it the same?

To see the compiler's point of view, I simplified your code to essential elements.

// Paste into Playgrounds and test for yourself!
enum FunctionError: Error { case reachedMax, unknownError }

// Why does the compiler not like this code?
func findUltimateAnswer(_ loopMax: Int) throws -> Int {
    for loopCounter in 1...loopMax {  // <-- Change to loopMax - 100 as a test.
        // Note: This loop may NEVER run!
        if loopCounter >= loopMax {
            return 42 // <-- Ultimate answer.
        }
    }
    // It's possible to reach here
    // without the loop above ever reaching the return statement!  
    // What gets returned then???

    // throw FunctionError.unknownError   // <-- Uncomment and try to run again.
}

let ultimateAnswer = try findUltimateAnswer(100)  // <-- It's 42, amirite?
print("The ultimate answer: \(ultimateAnswer)") 

let wonkyAnswer = try findUltimateAnswer(0)       // <-- It's 42, amirite?
print("The ultimate answer: \(wonkyAnswer)")      // What is returned?

From this snip, convince yourself that the loop inside the function may never run. Consequently, the function never has the opportunity to return an Int as your function advertises.

Because your code can throw errors, it makes sense to add throw FunctionError.unknownError as the very last line in your function.

1      

Thanks for the help. It makes sense after your post. I've tested on a playground with the value 0 and i understand the error it gives because with that value, the range would be from 1 to 0, but in that case, since the loop doesn't run, instead of giving me a system error like this: Swift/x86_64-apple-macos.swiftinterface:5782: Fatal error: Range requires lowerBound <= upperBound

Shouldn't it give the error string i used for that specific enum value in my code?

   

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Renato found a bug!

i understand the error it gives because with that value, the range would be from 1 to 0,
but in that case, since the loop doesn't run, Range requires lowerBound <= upperBound
Shouldn't it give the error string i used for that specific enum value in my code?

Nice code review. You found the bug, you found the root cause. But you missed on the way to report it.

Hint: What error condition might you add to your FunctionError enum?

Next, figure out how to catch that error when it occurs and how to throw the error back to the calling function!

Keep Coding!

Report back and show your revised code! We'd like to see what you learned.

   

I came up with this:

enum SquareError: Error {
    case outOfBounds, noRoot, fatalError
}

func findRoot(_ num: Int) throws -> Int{
    if num == 0 {
        throw SquareError.fatalError
    }

    for i in 1...num{
        if num < 1 || num > 10_000{
            throw SquareError.outOfBounds
        }

        if num == i * i{
            return i
        }

    }

    throw SquareError.noRoot
}

let userNum = 0

do {
    let result = try findRoot(userNum)
    print("The square root of \(userNum) is \(result)")
}catch SquareError.outOfBounds{
    print("Your number is out of bounds")
}catch SquareError.noRoot{
    print("No root found...")
}catch SquareError.fatalError{
    print("Critical Error!")
}catch{
    print("An error has occurred...")
}

I tried to catch the "value = 0" error doing an if statement before the for loop and it worked. Is this what you were expecting or there's a better solution for this? I'm asking this because i covered this problem with the value 0 but i feel like that if another error occurred, my default catchstill wouldn't appear, like the last version of this code...

When does the default catch error gets in action?

   

for i in 1...num{
    if num < 1 || num > 10_000{
        throw SquareError.outOfBounds
    }

    if num == i * i{
        return i
    }

}

Since num will always be constant during a call of your findRoot function, you are wasting a lot of effort on checking that it is between 1 and 10,000 every time through this loop. This check should be done up where you are already checking that num is not 0.

2      

Renato starts to bug-proof his code! Excellent progress.

if num == 0 {
        throw SquareError.fatalError
    }

Hope you don't think we're ganging up on you! Glad to see you revisit your code with updates!

As you gain experience, and kill more bugs(!) you'll start to think of the edge cases and build these checks in from the start. Indeed you fixed the one case where your input number is zero. But you could still accidentally try to pass in a negative number causing the same crash.

So consider this instead:

if num < 1 {
        throw SquareError.fatalError
    }

Then if you try zero or lower, your code will throw the error!

In future lessons, you'll love deploying your guards to protect your functions.

See -> Castle Guards

Keep Coding!

1      

Thank you all for the input. Reading roosterboy comment, decided to change my code. I think it's more simplified than before, and ended up resolving the fact that it was always checking if my number was between 1 and 10000. With this, even if i type "0" as my number i will have the error that i wrote instead of the system one.

Now the function will first see if my number is within bounds and if it is, it will move on to the next part, instead of continuously looping between the range of 1 and the choosen number to see if it's always between bounds.

func findRoot(_ num: Int) throws -> Int{
        if num < 1 || num > 10_000 {
            throw SquareError.outOfBounds
        }

        for i in 1...num{
           if num == i * i{
                return i
           }else{
                throw SquareError.noRoot
            }

    }
        throw SquareError.fatalError

}

   

Renato posts an update:

Now the function will first see if my number is within bounds and if it is, it will move on to the next part, instead of continuously looping between ......

I tried your code with:

try findRoot(25)  // <-- Should return 5. 

Your code throws a .noRoot error. Maybe take another look?

   

Yeah, should have tested with more numbers instead of only 0. So the way i had my code, it would throw the noRoot error instantaneously in the first loop of the for in because 1 * 1 isn't 25. So i basically cleaned my enum and removed the fatalError one, because in the flow of the code, if it reached there, was because there was a noRoot error.

Here's my code:

enum SquareError: Error {
    case outOfBounds, noRoot
}

    func findRoot(_ num: Int) throws -> Int{
        if num < 1 || num > 10_000 {
            throw SquareError.outOfBounds
        }

        for i in 1...num{
           if num == i * i{
                return i
           }

    }
        throw SquareError.noRoot

}

let userNum = 0

do {
    let result = try findRoot(userNum)
    print("The square root of \(userNum) is \(result)")
}catch SquareError.outOfBounds{
    print("Your number is out of bounds")
}catch SquareError.noRoot{
    print("No root found...")
}catch{
    print("An error has occurred...")
}

1      

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.