Kotlin from the ground up

Values and types

Val and var, the handful of types you meet first, whole numbers against decimals, and converting between them on purpose.

Part 2 of 12

Beginner15 min readBack to Courses

Val means fixed, var means changeable

val gives a name to a value that will not be reassigned. var gives a name to a value that will change as the program runs. Both are written the same way — the keyword, the name, an equals sign, the value — and both let the compiler work out the type from the value. The choice is not about how long the value lives; it is about whether the name may be pointed at a different value later.

Reach for val first. Most values in a clear program are decided once and never replaced, and the compiler will tell you about the one place that tries to change a val instead of letting a bug reach the reader.

The types you meet first

Int is a whole number, Double is one with a decimal point, String is text, Boolean is true or false, and Char is a single character in single quotes. You rarely write the type: val price = 12.5 already says Double, because 12.5 has a decimal point.

Kotlin will not quietly mix them, and the arithmetic shows it: 7 / 2 is 3, because two whole numbers divide to a whole number and the remainder is dropped. Ask for a Double anywhere in the sum — 7.toDouble() / 2 — and you get 3.5. The rule is not about the answer being whole; it is about the types of the two values before the division starts.

Converting on purpose

Text that holds a number becomes a number with toInt() or toDouble(), and a number becomes text with toString(). Each of those is a decision you made, which is the point: Kotlin makes conversion visible instead of hiding it in an operator. A conversion is also where a bad value is caught, so the line that converts is the line to read when a number looks wrong.

toInt() cuts the fraction off rather than rounding, so 12.5 becomes 12. Text that is not a number at all throws instead of quietly becoming zero — a loud failure you can fix, rather than a wrong total you cannot see. Rounding is a separate decision: add 0.5 before converting when you want the nearest whole number, and say so in the code.

Reading a type error

A type error is the compiler telling you where two decisions disagree. A declaration that promises an Int while the value on the right is text fails on that line, because the two sides are not the same kind of thing. The fix is a conversion, not a different name: "3".toInt() produces the number the declaration asked for.

The message usually quotes the type it found and the type it expected. Read both before editing, because the line it points at is often correct and the mistake is earlier — a value that arrived as text from a template, or a division that produced an Int where a Double was needed. Change the place where the wrong type was created, not the place where it was noticed.

A name, a value, a type

fun main() {
    val city = "Kathmandu"   // cannot be reassigned
    var visits = 3           // can be reassigned
    val fare = 2.5           // Double
    val open = true          // Boolean
    val initial = 'K'        // Char

    visits = visits + 1
    println(visits)          // 4

    val whole = 7
    val part = 2
    println(whole / part)             // 3
    println(whole.toDouble() / part)  // 3.5
    println("42".toInt() + 1)         // 43
}

A small packing list of values

Run it and match each printed line to the line of code that produced it. Then add a value of your own, print its type with plus signs, and try dividing two whole numbers and then one Double to see both answers.

Output
Click "Run" to execute your code

The type is fixed when the name is made

The compiler gives every name a type once, at the line where it is declared, and keeps it for the rest of the function. val price = 12.5 makes a Double and val count = 3 makes an Int; neither can later hold the other kind. You can leave the type out because the compiler infers it from the value, but the inference is not a guess: it is a decision every later line is checked against.

That is why conversion is written rather than implied. Int and Double are different types with different arithmetic, so Kotlin makes you say which one you mean. The same rule catches a total that would otherwise be text: once a value is a String, adding a number to it stays text, and the compiler will not quietly turn it back into arithmetic. The fix is to convert before the addition, while the value is still a number in your hands.

Three mistakes account for most type errors. Reassigning a val is refused at compile time, so use var only for the name that really changes. Dividing two Int values drops the fraction before you see it, so convert one side with toDouble() when the fraction matters. And toInt() truncates rather than rounds: 12.9.toInt() is 12, not 13.

Try these next

  • Convert 2.5 to an Int and print it, then convert 2.5 to a Double and compare the two answers.
  • Write a total in cents as an Int, then print the same amount as a Double with a decimal point.
  • Try to reassign a val and read the compiler message before you change it to var.

A fare, and the fractions it hides

Run it and find the line where a fraction disappears. Then change the number of people to 5, add a `Char` for the ride class, and print a total that mixes an Int and a Double.

Output
Click "Run" to execute your code

The compiler refuses a val that is reassigned, before the program runs at all. That is the strictness being useful: a mistake you would have found the hard way becomes a message on the line that caused it.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does val promise about a name?