Support Guard Control like swift

sometimes we have define the option type such as “var a: ClassA? = null” ,and then i need check a :

val realA = a?:return 
if(realA.somePr == true){
//do ....
}
//....

.but i want to use it like swift :

**guard** let realA = a else {
     return
}
if(realA.somePr == true){
//do ....
}
//....

how to impl it?

as i know swift :

guard let unwrappedString = wrappedString ,let bunwrappedString = bwrappedString where .....else {
  return
} // unwrappedString can be accessed outside guard block

kotlin

val unwrappedString = wrappedString?.let { it } ?: return
// unwrappedString is accessible outside this block 

but not powerful

3 Likes

inline infix fun T.or(orBlock: () -> T) = if (this == null) orBlock() else this

The “inline” keyword did the trick. Thanks!

Solution (doesn’t have ‘let’ semantics but at least has control flow):

inline fun guard( condition: Boolean, body: () -> Void ){
	if( !condition )
		body()
}
1 Like

I came up with this:

fun guard(predicate: () -> Boolean) : Guard {
    return Guard(predicate)
}

data class Guard(val predicate: () -> Boolean) {
    infix fun orElse(trap: () -> Nothing) {
        if (!predicate()) {
            trap()
        }
        // can we assert as post-condition that `predicate` holds?
    }
}

Note “return” type Nothing which is to imitate the semantics of Swift’s guard (which are not allowed to fall through).

Syntactically, this comes close (no assignment, of course):

fun foo(a: Int?) {
    guard { a != null } orElse {
        return@foo // would make sense, but isn't allowed
        throw RuntimeException("blerg") // throwing is what you do
    }
    assert(a != null) // _We_ know that, but Kotlin doesn't

    println(a + 1) // no smart cast :(
}

As the comments indicate,

  • we can not escape the orElse “branch” by returning from a surrounding scope, and
  • Kotlin is not able to infer that the guarding predicate is true if orElse returns.

The second point bothers me. Even if the compiler can’t infer the assertion (if feeld like it should be able to, in principle, at least for “simple” predicates) on its own, if we could tell it that predicate() ==> trap() called or orElse() terminates => predicate(), it could help it along. Are there some post-condition annotations that can do that?

That we don’t get a smart-cast after the assertion is so weird I’m inclined to call bug.

I guess the null check is just an example, because otherwise you could just use

a ?: run { ... }

That way kotlin also knows about a not being null later.

I’m not entirely sure why that is. Have you tried declaring orElse as inline? It might also have to do something with the declared return type of Nothing (just speculation though).

There is a keep about that here. There is a timeline in there as well. You can start to see the first implementation in Kotlin 1.3.

1 Like

That does it. Good call, thanks!
(Not 100% why non-line doesn’t work… probably something about which assumptions you can make about where that closure is passed to?)

Keeping the code in a Gist.

Something to look forward to, then!

In Swift guard takes the same expressions as if even though all examples emphasizes unwrapping (converting from nullable to not-nullable in Kotlin terms).

How about

fun <T> T.guard(vararg cond: Boolean, otherwise: T.()->Nothing): T {
    if(cond.all { !it }) {
        return this
    } else {
        otherwise()
    }
}

or for unwrapping:

fun <T> T?.guard(vararg cond: T.()->Boolean, otherwise: T?.()->Nothing): T {
     val value = this ?: otherwise()

    if(cond.all { value.it() }) {
         return value
    } else {
        otherwise()
    }
}

They unlike Swift’s guard allows to check multiple conditions. Also return in case of unacceptable parameters doesn’t make sense. This is why I made the handler to return Nothing, which will force the closure to throw Exception.

With Kotlin 1.3, the following is indeed possible:

inline fun guard(predicate: Boolean, orElse: () -> Nothing) {
    contract {
        returns() implies predicate
        callsInPlace(orElse, InvocationKind.AT_MOST_ONCE) // probably redundant
    }

    if (!predicate) {
        orElse()
    }
}

fun foo(a: Int?) {
    guard(a != null) {
        return@foo
    }

    println(a + 1) // smart cast!
}

The orElse DSL I proposed above can not (yet?) be implemented since contracts are only implemented for top-level functions. Similarly, a closure predicate is not (yet?) supported.

Note also that we’re not (yet?) able to express calls(orElse) implies !predicate or, equivalently, predicate implies callsInPlace(orElse, AT_LEAST_ONCE) which would be needed to fully describe the contract of guard. While that’s redundant here because of Nothing, for any other return type we’d need it.

1 Like

I found a surprasing way of achiving guard-like behaviour with simple if statement

val str: String? = null
if (str != null) else {
   return
}

println(str.length) // smart cast to String

The only differense with Swift’s guard is that guard requires to call return and if doesn’t.

You can simplify that to:

val str: String = null ?: return

println(str.length)

I know, that was just an example.
What I meant is that you can have as many condition checks as you want, with similar to guard syntax.

    val value: Any = "Hell"

    if (value is String && value.length >= 5) else {
       log("value is either not a string or is to short, returning")
       return
    }

    log("Char at index 4 is ${value[4]}")

I would write it like this:

val value = nullableString?.takeIf { it is String && it.length >= 5 } ?: return Unit.also {
    log("value is either not a string or is to short, returning")
}

log("Char at index 4 is ${value[4]}")

Also note that if (str == null) return also works instead of your original if (str != null) else { return }.

You can, I just proposed how you can do it with if acting like guard

I would suggest not to use code like that. It is very easy to misread it. I didn’t notice there was an else at all when I read it the first time. Not seeing the else made me think the condition for returning is inversed. This can lead to misinterpretations and hence bugs.

2 Likes

I agree, this approach is not obvious and takes time to get used to (and I don’t use it), but again, this is just one more way to mimic guard.

And that is the feature of guard I was looking to emulate! It’s not (only) about what is executed, but more about what the compiler can require and assume.

if you are looking to use some variables if all are not optional and a condition is met would you consider something like this

inline fun <T1, T2, T3, T4, R> guard(
    p1: T1?, p2: T2?, p3: T3?, p4: T4?,
    condition: Boolean = true,
    block: (T1, T2, T3, T4) -> R
): R? =
    if (p1 != null && p2 != null && p3 != null && p4 != null && condition)
        block(p1, p2, p3, p4)
    else null

this function allows you to run code if all the optionals values are non null.

so if you want to avoid

name?.let{nm -> 
    email?.takeIf{  it.isValid }?.let{ em ->
       password?.let{ pw ->
             createUser(nm, em, pw)
       }
    }
}

this then becomes

guard(name, email, password, condition = email?.isValid ?: false ) { nm, em,pw ->
   createUser(nm, em, pw)
}

Trying to get if or even the elvis operator to operate as guard feels like a pretty sub par attempt and seems to miss the more useful aspects of guard.

For something like this:

guard let a = self.nullableA, let b = self.nullableB, let c = self.nullableC else {
   log("something")
   return
}

The Kotlin version:

val a = this.nullableA
val b = this.nullableB
val c = this.nullableC

if(a == null || b == null || c == null) {
   log("something")
   return
}

mark_gilchrist captures a nice solution, however, a baked in language feature (along with a ternary operator :slight_smile: would be heaps closer to concise syntactic sugar nirvana for me.

Sorry, but this is one of my bugbears… :slight_smile:

Kotlin has a ternary operator! In fact, any operator with three operands is ternary; but Kotlin has the same ternary operator as Java (and C). It’s spelled differently: if (…) … else … instead of … ? … : …, but it means exactly the same. In particular, it’s an expression that gives a value, so can be used in all the same places as Java’s one. And while it takes a few extra characters to type, it’s arguably clearer and easier to read.

I really don’t understand why people keep asking for something that’s already there…

1 Like

Lol all threads lead to a discussion on ternary–even after we got an entire KotlinConf about not adding it! :wink:

We should probably not get off topic though.

1 Like