Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
idrougge committed Jun 14, 2018
0 parents commit 3ab18a9
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Guard for Kotlin

## What? ##
This generic function adds a mechanism similar to the `guard` statement in Swift to Kotlin. The `guard` mechanism checks a precondition (usually a `null` check), optionally binding variables if the precondition worked out. If a precondition fails, an `else` block is executed, which must end with a statement to leave the current scope, forcing an early return.

## Usage ##
In Swift, this is expressed as:
```swift
guard let aa = a else { return }
```
However, this implementation follows the usual pattern of Kotlin standard helper function such as `apply` or `let`:
```kotlin
val w = v.guard{
println("Value was null!")
return
}
```

## Advanced usage ##
Unlike Swift's `guard`, which can take any number and kind of preconditions, this leans on other standard Kotlin functions for additional conditions to be tested, e.g.:
```kotlin
val w = value
.takeIf { it < 256 }
.guard {
println("value was either null or too big")
return
}
```

## Rationale ##
The canonical Kotlin way to check for `null` and otherwise return is either using the "Elvis" operator `?:` or a construct such as `let{}` or `apply{}`. Using an "Elvis return", a function contains expressions such as:
```kotlin
val unwrapped = maybe ?: return // With optional return value
```
The right-hand side of an Elvis expression can only be a single statement, even though you may want to log the error before the return, or forcing a retry.

The `let` or `takeIf` methods either require the entire logic for the "safe" value to be moved into the accompanying block, building upon the "pyramid" structure of code, or assignment followed by another Elvis for an early return.

Using a `guard`, any error handling takes place in the adjacent block, while the "usual" logic of the function goes on below the guard, at the usual indentation scope and indentation level. The guard also forces you to exit early, facilitating reasoning about the core logic of the function at hand, since each time you see a `guard`, you know that one source of errors has been excluded.

## Example code ##
```kotlin
fun test() {
var maybe:String? = "something" // maybe is a nullable string
println(maybe is String) // true

val unwrapped = maybe.guard{
println("This would only run if `maybe` was null")
return
}
println(unwrapped is String) // true

maybe = null
println(maybe is String) // false

val never = maybe.guard{
println("Found a null value, exiting function")
return
}
println("This line is unreachable because `maybe` is set to null")
}

fun main(args: Array<String>) {
test()
println("Back in main")
}
```
9 changes: 9 additions & 0 deletions guard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Checks if the receiver is `null` and if so, executes the `nullClause`, forcing an early exit.
* @param nullClause A block to be performed if receiver is null.
* This block must end with a `return` statement, forcing an early exit from surrounding scope on `null`.
* @return The receiver, now guaranteed not to be null.
*/
inline fun<T> T?.guard(nullClause: () -> Nothing): T {
return this ?: nullClause()
}

0 comments on commit 3ab18a9

Please sign in to comment.