2

I'm trying to understand why let is needed. In the example below I have a class Test with a function giveMeFive:

public class Test() {
    fun giveMeFive(): Int {
        return 5
    }
}

Given the following code:

var test: Test? = Test()

var x: Int? = test?.giveMeFive()

test = null

x = test?.giveMeFive()
x = test?.let {it.giveMeFive()}

x gets set to 5, then after test is set to null, calling either of the following statements return null for x. Given that calling a method on a null reference skips the call and sets x to null, why would I ever need to use let? Are some cases where just ?. won't work and let is required?

Further, if the function being called doesn't return anything, then ?. will skip the call and I don't need ?.let there either.

1
  • 1
    fun giveMeFive() = 5 Commented Jan 26, 2019 at 21:15

4 Answers 4

11

let()

fun <T, R> T.let(f: (T) -> R): R = f(this)

let() is a scoping function: use it whenever you want to define a variable for a specific scope of your code but not beyond. It’s extremely useful to keep your code nicely self-contained so that you don’t have variables “leaking out”: being accessible past the point where they should be.

DbConnection.getConnection().let { connection ->
}

// connection is no longer visible here

let() can also be used as an alternative to testing against null:

val map : Map<String, Config> = ...
val config = map[key]
// config is a "Config?"
config?.let {
// This whole block will not be executed if "config" is null.
// Additionally, "it" has now been cast to a "Config" (no 
question mark)
}

A simple go to chart when to use when.

Sign up to request clarification or add additional context in comments.

1 Comment

For more clarification in kotlin android: medium.com/@khadijahameed415/…
4

You need to use let if you want to chain function calls that aren't defined on the type you are chaining from.

Let's say the definition of your function was this instead:

// Not defined inside the Test class
fun giveMeFive(t: Test) {
    return 5
}

Now, if you have a nullable Test and want to call this function in a chain, you have to use let:

val x = test?.let { giveMeFive(it) }

1 Comment

let helps when you want to write a one-liner function, but without it, your operations take more than one line. Silly example: "a=b".split("=").let { Pair(parts[0], parts[1]) }. Without let, it takes two lines, therefore can't be a one-liner function.
2

The .let{} extension function in Kotlin:

  1. Takes the object reference as the parameter on which .let{} is called.

  2. Returns value of any non-primitive data-type which has been returned from with let{} function. By default, it returns undefined value of kotlin.Any class.

Declaration in package kotlin:

public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}

Simple practical demonstration to see how .let{} extension function works in Kotlin.

Sample Code 1:-

val builder = StringBuilder("Hello ")
println("Print 0: $builder")

val returnVal = builder.let { arg ->
    arg.append("World")
    println("Print 1: $arg")

    "Done" // Returnning some string
}
println("Print 2: $builder")
println("Print 3: $returnVal")

Sample Code 1 Output:-

Print 0: Hello 
Print 1: Hello World
Print 2: Hello World
Print 3: Done

In Sample Code 1:

  1. We created the final object of type StringBuilder with initialization value "Hello ".

  2. In builder.let{}, the builder object reference will be passed to arg.

  3. Here, the output Print 2: Hello World and Print 3: Hello World means that the builder and arg, both are pointing to the same StringBuilder object-reference. That's why they both print the same String value.

  4. In the last line of .let{} function block, we are returning "Done" String value which will be assigned to returnVal.

*Hence, we get Print 3: Done output from returnVal.

Sample Code 2:-

val builder = StringBuilder("Hello ")
println("Print 0: $builder")
val returnVal = builder.let { arg ->
    arg.append("World")
    println("Print 1: $arg")
    arg.length
    }

println("Print 2: $builder")
println("Print 3: $returnVal") // Now return val is int = length of string.

Sample Code 2 Output:-

Print 0: Hello 
Print 1: Hello World
Print 2: Hello World
Print 3: 11

In Sample Code 2:

What's difference:

In the last line of .let{} function block, we are returning the Int value equals to the length of StringBuilder object arg whose value at this line of code is "Hello World". So length = 11 of type Int will be assigned to returnVal.

*Hence, we get Print 3: 11 as output from returnVal.

Also try these:-

  • .run() {}
  • .apply() {}
  • with(obj) {}
  • .also() {}

Happy Coding...

1 Comment

Other than the five you've mentioned, there's one more defined for Closeable, use
0

fun <T, R> T.let(f: (T) -> R): R = f(this)

.let block does not equal to in multiThreading

val x? = null

if(x == null) {
}

and

x?.let{
}

.let block is thread-safe

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.