Elvis operator (?:) always returns the left operand (.toString())

Given the following code :

val statusCode: String = result?.httpStatusCode?.statusCode.toString()
            ?: Response.Status.NO_CONTENT.statusCode.toString()

Why do I get the error :
Elvis operator (?:slight_smile: always returns the left operand of non-nullable type String

If I understand the “?” correctly, result could be null or httpStatusCode could be null and therefore the toString function could never be called, requiring the right side of the operator?

What is the type of result variable and httpStatusCode, statusCode members? It looks they are not nullable.

toString() is defined for null types so that it returns a non-nullable String. What you need to do is add another ?. after status code like so:

result?.httpStatusCode?.statusCode?.toString()
            ?: Response.Status.NO_CONTENT.statusCode.toString()

Although, I would suggest you do this instead:

(result?.httpStatusCode ?: Response.Status.NO_CONTENT).statusCode.toString()
3 Likes

Hi.

result is defined as result: ErrorCodes?
httpStatusCode as val httpStatusCode: Response.Status,

Thanks for the solution, that looks like a great way to do it.

What I’m struggling with is why the toString function would be called if result is null. I know toString works with a null receiver, but my understanding of the ? was that it implemented a null check. If it does perform a null check, why would toString ever be invoked?

? is not an operator; the operator here is ‘?.’. It’s called the safe-call operator: if its LHS is non-null, it calls the RHS function/property, and returns that; otherwise, it returns null.

In your code, note that the last operator is ‘.’, not ‘?.’:

result?.httpStatusCode?.statusCode.toString()

To clarify the operator precedence, that means the same as:

(result?.httpStatusCode?.statusCode).toString()

The (…) expression may evaluate to null (if result is null, or its httpStatusCode is null, or if its statusCode is null), or to a non-null value — but either way, toString() will be called on the result. And since toString() returns a non-nullable String, then the whole expression never evaluates to null.

1 Like

Thank you. That clarifies the why nicely.

I think I’m missing something fundamental about how these operators work. My expectation is that toString is a method/function on the Int object and as such would be called (or not) based purely on whether I reach that point. (The Int object)

So result?. returns a null and the operation stops there. I would not have expected the evaluation to continue to httpStatusCode and then again to statusCode.

In Java I would have received a null pointer exception at result.getHttpStatusCode(). It would never have evaluated the next value.

If I’m understanding the above correctly though, the safe call operator does not stop the operation, it just returns a null.

So we’d have result?. evaluating to null and then (get)httpStatusCode would be called on that null value (and that’s why I needed the safe call operator at that point because even though it’s not a nullable variable, it is nullable now because of the previous ?.)

Then (get)statusCode would be called on httpStatusCode null value returned by the safe operator.

Then toString is called on the result and the reason I don’t need a forced ?. on that is because toString excepts a null receiver…?

If that is how it’s working, what would be the result of this call?

result?.httpStatusCode?.statusCode?.let { toString() } // Is let still called on the null?

Also, why does this return a different value? (If I use the null safe operator on the last variable/statusCode)

result?.httpStatusCode?.statusCode?.toString() // Why does this not invoke toString with a null and therefore return an empty string

Yes, but that’s not the toString() that’s being called here…

The top-level class Any has a toString() method, so every object can be converted to a string. Every other class can override that method (and it’s often a good idea to do so). So yes, Int has a toString() that converts the number to a string in the usual way.

However, there’s also a built-in top-level extension function: fun Any?.toString(). An extension function is a method that’s defined outside of a class, but has some syntactic sugar so calls to it look like calling a method of the class.

Note that in this case it’s an extension on Any?, which is nullable — and so (unlike normal methods), it can be called on a null. (The implementation for that function isn’t public, but the effect is simple: it returns "null" when called on a null, else it calls the object’s own toString() method. On Kotlin/JVM, it may translate to java.lang.String.valueOf(Object), which does that.)

Because your code is calling toString() on a nullable value, the extension function is the only possible toString() you could be calling, and so that’s what the compiler selects.

When you change the last operator to a safe-call ‘?.’, then the compiler knows that you’re calling it on a non-null object, and so it selects the more specific Any.toString() method instead.

(These are all standard language features — though nullable receivers aren’t common, and this is probably the first one many people see.)

If you’re using an IDE such as IntelliJ, it can often help you see what’s going on in cases such as this: try hovering over or right-clicking or Cmd+tapping an operator or function call to see which function is actually being called.

2 Likes

Here is what’s happening when result?.httpStatusCode?.statusCode.toString() is evaluated:

  • First Kotlin evaluates result?.httpStatusCode. It looks at the left side and sees result is null, so this evaluates to null.
  • Then it evaluates the next part of the statement, which is now null?.statusCode. Once again the left side is null, so this evaluates to null.
  • The next part of the statement is now null.toString(), so Kotlin calls the Any?.toString() extension function witch returns a string.
2 Likes

I feel like this hasn’t been properly answered yet in this thread. With your last remark, you are correct. The safe call (?.) is not short-circuiting anything, it’s a binary operator with a clear definition of what it returns for which operands. The left operand can be any nullable value and the right operand is a member expression for the type of that value. Safe call is defined as:

  • if the left operand is not null, the result of the expression is the result of the member expression on the right
  • if the left operand is null, the result of the expression is null

At no point is the execution “stopped” early or anything, it’s just skipping the member expression and returning null.

The key here is the term “expression”, because it means that it has to return a value. ?. can be used in expressions (and not just in statements), therefore it needs to return a value in every possible case.

1 Like

As you say, the operator evaluates its right-hand side only if the left-hand side evaluates to non-null; it does not evaluate its right-hand side otherwise.

This behaviour is an example of what’s usually called short-circuit evaluation, in exactly the same way as the && and || operators in Kotlin (and many other languages). The official Kotlin docs make this explicit; the reference for the and function says that:

Unlike the && operator, this function does not perform short-circuit evaluation.

1 Like

An alternative way of writting subject?.function() would be something like optional.letNotNull { it.function() } or maybe subject.onNotNull {...}

I get a feeling that the confusion revolves around chaining the calls? Here’s what that would look like using our letNotNull() function:

fun <T, R> T?.letNotNull(block: (T) -> R): R? {
  return if (this != null) block(this) else null
}
fun <T, R> T?.letNull(block: (T?) -> R): R? {
  return if (this == null) block(this) else this
}

val result = 5
val Any.httpStatusCode get() = 5
val Any.statusCode get() = 5
object Response {
    object Status {
      val NO_CONTENT = 5
    }
}
fun main() {
//sampleStart
// OP's original sample
/*
result?.httpStatusCode?.statusCode.toString()
    ?: Response.Status.NO_CONTENT.statusCode.toString()
*/
result // Could return null or HttpResponse
    .letNotNull { it.httpStatusCode } // Could return null or HttpStatusCode
    .letNotNull { it.statusCode } // Could return null or StatusCode
    .toString()  // Can only return String
    .letNull { Response.Status.NO_CONTENT.statusCode.toString() }

// kyay10's fixed sample
/*
result?.httpStatusCode?.statusCode?.toString()
    ?: Response.Status.NO_CONTENT.statusCode.toString()
*/
result // Could return null or HttpResponse
    .letNotNull { it.httpStatusCode } // Could return null or HttpStatusCode
    .letNotNull { it.statusCode } // Could return null or StatusCode
    .letNotNull { it.toString() } // Could return null or StatusCode
    .letNull { Response.Status.NO_CONTENT.statusCode.toString() }
//sampleEnd
}
2 Likes