Exit Full View

Kotlin Extension Functions are Not Methods

Take a look at this code :

  1. class Foo {
  2. var thingy : Something? = null
  3. fun foo() {
  4. thingy.blah()
  5. }
  6. }

Will it compile? thingy is nullable, so we have to guard against null by using thing?.blah() right?

Wrong!

If blah() is an extension function, then this is really syntactic sugar for blah( thingy ).

Functions can take nullable arguments, so this code is perfectly fine.

Buggy Code

Suppose we have a time in seconds, and we want to print it in mm:ss format.

  1. class Foo {
  2. var seconds = 0
  3. fun ellapsedString() : String {
  4. val mins = seconds / 60
  5. val secs = seconds - mins * 60
  6. return if (secs < 10) {
  7. "$mins:0$secs"
  8. } else {
  9. "$mins:$secs"
  10. }
  11. }
  12. }

Can you spot the bug? Read on if you can't!

A Possible Fix

  1. class Foo {
  2. var seconds = 0
  3. fun ellapsedString() : String {
  4. seconds.let { seconds ->
  5. val mins = seconds / 60
  6. val secs = seconds - mins * 60
  7. return if (secs < 10) {
  8. "$mins:0$secs"
  9. } else {
  10. "$mins:$secs"
  11. }
  12. }
  13. }
  14. }

've seen boat loads of example code on-line, and have never seen an example without the null guard ?.let{ ... }.

I bet most people don't even consider using it without the ?.

Here, we are using let as it was intended. It is called let, because it is an assignment. We take the field seconds and assign it to a local variable (which I've deliberately also called seconds). We then use the local variable to build the string.

We need this assignment because the field is a var. Without it the value can change (from another thread, or in more complex example, deep in our code from the same thread). If the value ticks from 59 to 60 while we are building up our string, we will get the result 0:00. Because it is getting the first 0 from 59 seconds, and the 00 from 60 seconds.

An Alternative Solution, without let{...}

  1. class Foo {
  2. var seconds = 0
  3. fun ellapsedString() : String {
  4. val seconds = this.seconds
  5. val mins = seconds / 60
  6. val secs = seconds - mins * 60
  7. return if (secs < 10) {
  8. "$mins:0$secs"
  9. } else {
  10. "$mins:$secs"
  11. }
  12. }
  13. }

And now it's even more obvious that let{ ... } is really an assignment!

Deliberately Reuse the Same name

Why did I deliberately use the name seconds twice?

It is good practice to hide the field, to avoid bugs like this :

  1. class Foo {
  2. var seconds = 0
  3. fun ellapsedString() : String {
  4. seconds.let {
  5. val mins = seconds / 60 // Oops, we should have used 'it' not 'seconds'
  6. val secs = seconds - mins * 60 // Oops, we did it again.
  7. return if (secs < 10) {
  8. "$mins:0$secs"
  9. } else {
  10. "$mins:$secs"
  11. }
  12. }
  13. }
  14. }

Other Scope Functions

let isn't the only scope function, there are 5 standard ones :

let, run, with, apply, and also.

apply and also are extension functions, and can be applied to nullable values.