As @Jumhyn has written out, the way to express this behavior in Swift is to return a non-optional value and to use fatalError in cases where there is no value to return.

An actual return type of String! is not actionable for the caller: there would be no way to handle the error, and there can be no custom error message detailing why.

As you’ve noticed, because String! is no longer its own type, the user will get a return value of type String? and the chance to handle the nil case. This is the desired behavior: it would be undesirable to write out return nil in a context where no nil value is actually returned to the user.

3 Likes

Why is it even possible to use ! on a return type?

This declares the return type as an implicitly unwrapped optional, which will allow it to convert to a non-optional (by force unwrapping) when the used directly in a context that expects a non-optional. If there’s no type context that forces the conversion, though, it will simply propagate as an optional type.

3 Likes

TL;DR:

func f() -> String! { "abc" }

func takesAnAny(_ value: Any) {
    print(value) // => Optional("abc")
}

func takesAString(_ str: String) {
    print(value) // => "abc"
}

// Without any contextual type information, the result of `f()` stays optional
// ℹ️ "Coercion of implicitly unwrappable value of type 'String?' to 'Any' does not unwrap optional"
takesAnAny(f())

// But when the context requires a non-optional, the implicitly-unwrapped optional is ... implicitly unwrapped.
takesAString(f())

In my own projects, I have some functions returning IOUs as a way to say "there's an edge case here that I would have fatalErrored on, but I'll give you a chance to handle it if you care about it".

  • Callers who don't care about the edge case, can just use the result in a non-optional context (triggering an implicit unwrap, and a crash on nil, just like the fatalError I would have used if I didn't use an IOU)
  • Callers who care about handling all cases thoroughly can conditionally unwrap the result.

For example, it could be useful to write:

// Good enough most of the time, but anyone who wants to make sure their
// code doesn't crash will need to remember to check their input before
// calling this. This is a misplaced responsibility, and is error-prone.
func fib_IOU(_ input: Int) -> Int {
	guard 0 <= input else {
        fatalError("Fib(n) is only defined for non-negative values")
    }
		
	if input == 0 || input == 1 { return 1 }
	
	return fib_IOU(input - 1) + fib_IOU(input - 2)
}

As:

// `Int?` would be too heavy-handed (it would be annoying to need to unwrap all usages),
// because most usages will be with non-negative numbers.
// `Int!` strikes a nice middle-ground
func fib(_ input: Int) -> Int! {
	guard 0 <= input else { return nil }
		
	if input == 0 || input == 1 { return 1 }
	
	return fib(input - 1) + fib(input - 2)
}

// Two usage patterns:

// 1. "quick and dirty"
let aNumberIKnowIsNotNegative = 10
let result: Int = fib_IOU(aNumberIKnowIsNotNegative)

// 2. "cover my ass"
let anUnknownInput = someUserInput()
if let result = fib_IOU(anUnknownInput) {
    print(result)
} else {
    print("you can't calculate fib on a negative number")
}
2 Likes

So it exists purely to avoid two overloads, yes?

func ƒ() -> Void? { () }
func ƒ() { ƒ()! }

This feels a lot like rethrows.

IIRC implicitly unwrapped optionals were originally added to ease the transition of Objective-C code with IBOutlets and such. They've largely been replaced by lazy and property wrappers.