After reading lesson 10, you’ll be able to
Previous lessons covered Booleans, strings, and a dozen different numeric types. If you have variables of different types, you must convert the values to the same type before they can be used together.
Say you’re at the grocery store with a shopping list from your spouse. The first item is milk, but should you get cow’s milk, almond, or soy? Should it be organic, skim, 1%, 2%, whole, evaporated, or condensed? How many gallons? Do you call your spouse to ask or just pick something?
Your spouse may get annoyed if you keep calling to ask for each detail. Iceberg or romaine lettuce? Russet or red potatoes? Oh, and was that 5 lbs. or 10? On the other hand, if you “think for yourself” and return with chocolate milk and french fries, that may not go over so well.
If your spouse is a programmer and you’re a compiler in this scenario, what do you think Go’s approach would be?
A variable’s type establishes the behavior that’s appropriate for it. Numbers can be added, strings can be joined. To join two strings together, use the plus operator:
countdown := "Launch in T minus " + "10 seconds."
If you try to join a number to a string, the Go compiler will report an error:
countdown := "Launch in T minus " + 10 + " seconds." 1
When presented with two or more different types, some programming languages make a best effort to guess the programmer’s intentions. Both JavaScript and PHP can subtract 1 from the string "10":
"10" - 1 1
The Go compiler rejects "10" - 1 with a mismatched types error. In Go, you first need to convert "10" to an integer. The Atoi function in the strconv package will do the conversion, but it will return an error if the string doesn’t contain a valid number. By the time you handle errors, the Go version is four lines long, which isn’t exactly convenient.
That said, if "10" is user input or came from an external source, the JavaScript and PHP versions should check whether it’s a valid number too.
In languages that coerce types, the code’s behavior is less predictable to anyone who hasn’t memorized a myriad of implicit behaviors. The plus operator (+) in both Java and JavaScript coerces numbers to strings to be joined, whereas PHP coerces the values to numbers and does the math:
"10" + 2 1
Once again, Go would report a mismatched types error.
Another example of mismatched types occurs when attempting a calculation with a mix of integer and floating-point types. Real numbers like 365.2425 are represented with a floating-point type, and Go infers that whole numbers are integers:
age := 41 1 marsDays := 687 1 earthDays := 365.2425 2 fmt.Println("I am", age*earthDays/marsDays, "years old on Mars.") 3
If all three variables were integers, the calculation would succeed, but then earthDays would need to be 365 instead of the more accurate 365.2425. Alternatively, the calculation would succeed if age and marsDays were floating-point types (41.0 and 687.0 respectively). Go doesn’t make assumptions about which you’d prefer, but you can explicitly convert between types, which is covered in the next section.
What is "10" - 1 in Go?
Type conversion is straightforward. If you need the integer age to be a floating-point type for a calculation, wrap the variable with the new type:
age := 41 marsAge := float64(age)
Variables of different types don’t mix, but with type conversion, the calculation in the following listing works.
age := 41 marsAge := float64(age) marsDays := 687.0 earthDays := 365.2425 marsAge = marsAge * earthDays / marsDays fmt.Println("I am", marsAge, "years old on Mars.") 1
You can convert from a floating-point type to an integer as well, though the digits after the decimal point will be truncated without any rounding:
fmt.Println(int(earthDays)) 1
Type conversions are required between unsigned and signed integer types, and between types of different sizes. It’s always safe to convert to a type with a larger range, such as from an int8 to an int32. Other integer conversions come with some risks. A uint32 could contain a value of 4 billion, but an int32 only supports numbers to just over 2 billion. Likewise, an int may contain a negative number, but a uint can’t.
There’s a reason why Go requires type conversions to be explicitly stated in the code. Every time you use a type conversion, consider the possible consequences.
What code would convert the variable red to an unsigned 8-bit integer?
What is the result of the comparison age > marsAge?
In 1996, the unmanned Arianne 5 rocket veered off its flight path, broke up, and exploded just 40 seconds after launch. The reported cause was a type conversion error from a float64 to an int16 with a value that exceeded 32,767—the maximum value an int16 can hold. The unhandled failure left the flight control system without orientation data, causing it to veer off course, break apart, and ultimately self-destruct.
We haven’t seen the Arianne 5 code, nor are we rocket scientists, but let’s look at how Go handles the same type conversion. If the value is in range, as in the following listing, no problem.
var bh float64 = 32767 var h = int16(bh) 1 fmt.Println(h)
If the value of bh is 32,768, which is too big for an int16, the result is what we’ve come to expect of integers in Go: it wraps around, becoming the lowest possible number for an int16, –32768.
The Ada language used for the Arianne 5 behaves differently. The type conversion from float64 to int16 with an out-of-range value caused a software exception. According to the report, this particular calculation was only meaningful prior to liftoff, so Go’s approach may have been better in this instance, but usually it’s best to avoid incorrect data.
To detect whether converting a type to int16 will result in an invalid value, the math package provides min/max constants:
if bh < math.MinInt16 || bh > math.MaxInt16 { // handle out of range value }
These min/max constants are untyped, allowing the comparison of bh, a floating-point value, to MaxInt16. Lesson 8 talks more about untyped constants.
What code will determine if the variable v is within the range of an 8-bit unsigned integer?
v := 42 if v >= 0 && v <= math.MaxUint8 { v8 := uint8(v) fmt.Println("converted:", v8) 1 }
- 1 Prints converted: 42
To convert a rune or byte to a string, you can use the same type conversion syntax as numeric conversions, as shown in the next listing. This gives the same result using the %c format verb introduced in lesson 9 to display runes and bytes as characters.
var pi rune = 960 var alpha rune = 940 var omega rune = 969 var bang byte = 33 fmt.Print(string(pi), string(alpha), string(omega), string(bang)) 1
Converting a numeric code point to a string works the same with any integer type. After all, rune and byte are just aliases for int32 and uint8.
To convert digits to a string, each digit must be converted to a code point, starting at 48 for the 0 character, through 57 for the 9 character. Thankfully, the Itoa function in the strconv (string conversion) package does this for you, as shown in the next listing.
countdown := 10 str := "Launch in T minus " + strconv.Itoa(countdown) + " seconds." fmt.Println(str) 1
Itoa is short for integer to ASCII. Unicode is a superset of the old ASCII standard. The first 128 code points are the same, which includes digits (used here), English letters, and common punctuation.
Another way to convert a number to a string is to use Sprintf, a cousin of Printf that returns a string rather than displaying it:
countdown := 9 str := fmt.Sprintf("Launch in T minus %v seconds.", countdown) fmt.Println(str) 1
To go the other way, the strconv package provides the Atoi function (ASCII to integer). Because a string may contain gibberish or a number that’s too big, the Atoi function may return an error:
countdown, err := strconv.Atoi("10") if err != nil { // oh no, something went wrong } fmt.Println(countdown) 1
A nil value for err indicates that no error occurred and everything is A-OK. Lesson 28 navigates the perilous topic of errors.
Name two functions that can convert an integer to a string.
In Go, once a variable is declared, it has a type and the type cannot be changed. This is known as static typing, which is easier for the compiler to optimize, so your programs run fast. But attempting to use a variable with a value of a different type will cause the Go compiler to report an error:
var countdown = 10 countdown = 0.5 1 countdown = fmt.Sprintf("%v seconds", countdown) 1
Languages such as JavaScript, Python, and Ruby use dynamic typing instead of static typing. In those languages, each value has an associated type, and variables can hold values of any type. They would allow the type of countdown to change as the program executes.
Go does have an escape hatch for situations where the type is uncertain. For example, the Println function will accept both strings and numeric types. Lesson 12 explores the Println function in more detail.
The Print family of functions displays the Boolean values true and false as text. As such, the next listing uses the Sprintf function to convert the Boolean variable launch to text. If you want to convert to numeric values or different text, a humble if statement works best.
launch := false launchText := fmt.Sprintf("%v", launch) fmt.Println("Ready for launch:", launchText) 1 var yesNo string if launch { yesNo = "yes" } else { yesNo = "no" } fmt.Println("Ready for launch:", yesNo) 2
The inverse conversion requires less code because you can assign the result of a condition directly to a variable, as in the following listing.
yesNo := "no" launch := (yesNo == "yes") fmt.Println("Ready for launch:", launch) 1
The Go compiler will report an error if you attempt to convert a Boolean with string(false), int(false), or similar, and likewise for bool(1) or bool("yes").
In programming languages without a dedicated bool type, the values 1 and 0 often stand in for true and false, respectively. Booleans in Go don’t have a numeric equivalent.
With a humble if statement:
launch := true var oneZero int if launch { oneZero = 1 } else { oneZero = 0 } fmt.Println("Ready for launch:", oneZero) 1
- 1 Prints Ready for launch: 1
Let’s see if you got this...
Write a program that converts strings to Booleans:
The switch statement accepts multiple values per case, as covered in lesson 3.