diff options
Diffstat (limited to 'content/constants.article')
-rw-r--r-- | content/constants.article | 166 |
1 files changed, 115 insertions, 51 deletions
diff --git a/content/constants.article b/content/constants.article index 7258aa9..36c4edc 100644 --- a/content/constants.article +++ b/content/constants.article @@ -14,8 +14,10 @@ This post explains why that is and what it means. * Background: C -In the early days of thinking about Go, we talked about a number of problems caused by the way C and its descendants let you mix and match numeric types. -Many mysterious bugs, crashes, and portability problems are caused by expressions that combine integers of different sizes and "signedness". +In the early days of thinking about Go, we talked about a number of problems +caused by the way C and its descendants let you mix and match numeric types. +Many mysterious bugs, crashes, and portability problems are caused by expressions +that combine integers of different sizes and "signedness". Although to a seasoned C programmer the result of a calculation like unsigned int u = 1e9; @@ -29,7 +31,9 @@ Is it signed or unsigned? Nasty bugs lurk here. -C has a set of rules called "the usual arithmetic conversions" and it is an indicator of their subtlety that they have changed over the years (introducing yet more bugs, retroactively). +C has a set of rules called "the usual arithmetic conversions" and it is +an indicator of their subtlety that they have changed over the years (introducing +yet more bugs, retroactively). When designing Go, we decided to avoid this minefield by mandating that there is _no_ mixing of numeric types. If you want to add `i` and `u`, you must be explicit about what you want the result to be. @@ -38,20 +42,27 @@ Given var u uint var i int -you can write either `uint(i)+u` or `i+int(u)`, with both the meaning and type of the addition clearly expressed, but unlike in C you cannot write `i+u`. +you can write either `uint(i)+u` or `i+int(u)`, +with both the meaning and type of the addition clearly expressed, +but unlike in C you cannot write `i+u`. You can't even mix `int` and `int32`, even when `int` is a 32-bit type. This strictness eliminates a common cause of bugs and other failures. It is a vital property of Go. -But it has a cost: it sometimes requires programmers to decorate their code with clumsy numeric conversions to express their meaning clearly. +But it has a cost: it sometimes requires programmers to decorate their code +with clumsy numeric conversions to express their meaning clearly. And what about constants? Given the declarations above, what would make it legal to write `i` `=` `0` or `u` `=` `0`? What is the _type_ of `0`? It would be unreasonable to require constants to have type conversions in simple contexts such as `i` `=` `int(0)`. -We soon realized the answer lay in making numeric constants work differently from how they behave in other C-like languages. -After much thinking and experimentation, we came up with a design that we believe feels right almost always, freeing the programmer from converting constants all the time yet being able to write things like `math.Sqrt(2)` without being chided by the compiler. +We soon realized the answer lay in making numeric constants work differently +from how they behave in other C-like languages. +After much thinking and experimentation, we came up with a design that we +believe feels right almost always, +freeing the programmer from converting constants all the time yet being +able to write things like `math.Sqrt(2)` without being chided by the compiler. In short, constants in Go just work, most of the time anyway. Let's see how that happens. @@ -61,37 +72,47 @@ Let's see how that happens. First, a quick definition. In Go, `const` is a keyword introducing a name for a scalar value such as `2` or `3.14159` or `"scrumptious"`. Such values, named or otherwise, are called _constants_ in Go. -Constants can also be created by expressions built from constants, such as `2+3` or `2+3i` or `math.Pi/2` or `("go"+"pher")`. +Constants can also be created by expressions built from constants, +such as `2+3` or `2+3i` or `math.Pi/2` or `("go"+"pher")`. -Some languages don't have constants, and others have a more general definition of constant or application of the word `const`. -In C and C++, for instance, `const` is a type qualifier that can codify more intricate properties of more intricate values. +Some languages don't have constants, and others have a more general definition +of constant or application of the word `const`. +In C and C++, for instance, `const` is a type qualifier that can codify +more intricate properties of more intricate values. But in Go, a constant is just a simple, unchanging value, and from here on we're talking only about Go. * String constants -There are many kinds of numeric constants—integers, floats, runes, signed, unsigned, imaginary, complex—so let's start with a simpler form of constant: strings. -String constants are easy to understand and provide a smaller space in which to explore the type issues of constants in Go. +There are many kinds of numeric constants—integers, +floats, runes, signed, unsigned, imaginary, +complex—so let's start with a simpler form of constant: strings. +String constants are easy to understand and provide a smaller space in which +to explore the type issues of constants in Go. A string constant encloses some text between double quotes. -(Go also has raw string literals, enclosed by backquotes ``````, but for the purpose of this discussion they have all the same properties.) +(Go also has raw string literals, enclosed by backquotes ``````, +but for the purpose of this discussion they have all the same properties.) Here is a string constant: "Hello, 世界" -(For much more detail about the representation and interpretation of strings, see [[https://blog.golang.org/strings][this blog post]].) +(For much more detail about the representation and interpretation of strings, +see [[https://blog.golang.org/strings][this blog post]].) What type does this string constant have? The obvious answer is `string`, but that is _wrong_. -This is an _untyped_string_constant_, which is to say it is a constant textual value that does not yet have a fixed type. +This is an _untyped_string_constant_, which is to say it is a constant textual +value that does not yet have a fixed type. Yes, it's a string, but it's not a Go value of type `string`. It remains an untyped string constant even when given a name: const hello = "Hello, 世界" After this declaration, `hello` is also an untyped string constant. -An untyped constant is just a value, one not yet given a defined type that would force it to obey the strict rules that prevent combining differently typed values. +An untyped constant is just a value, one not yet given a defined type that +would force it to obey the strict rules that prevent combining differently typed values. It is this notion of an _untyped_ constant that makes it possible for us to use constants in Go with great freedom. @@ -119,7 +140,9 @@ or by forcing the issue with a conversion, like this: .play -edit constants/string4.go /START/,/STOP/ -Returning to our _untyped_ string constant, it has the helpful property that, since it has no type, assigning it to a typed variable does not cause a type error. +Returning to our _untyped_ string constant, +it has the helpful property that, since it has no type, +assigning it to a typed variable does not cause a type error. That is, we can write m = "Hello, 世界" @@ -128,10 +151,13 @@ or m = hello -because, unlike the typed constants `typedHello` and `myStringHello`, the untyped constants `"Hello,`世界"` and `hello` _have_no_type_. +because, unlike the typed constants `typedHello` and `myStringHello`, +the untyped constants `"Hello,`世界"` and `hello` _have_no_type_. Assigning them to a variable of any type compatible with strings works without error. -These untyped string constants are strings, of course, so they can only be used where a string is allowed, but they do not have _type_ `string`. +These untyped string constants are strings, +of course, so they can only be used where a string is allowed, +but they do not have _type_ `string`. * Default type @@ -140,7 +166,8 @@ As a Go programmer, you have certainly seen many declarations like str := "Hello, 世界" and by now you might be asking, "if the constant is untyped, how does `str` get a type in this variable declaration?" -The answer is that an untyped constant has a default type, an implicit type that it transfers to a value if a type is needed where none is provided. +The answer is that an untyped constant has a default type, +an implicit type that it transfers to a value if a type is needed where none is provided. For untyped string constants, that default type is obviously `string`, so str := "Hello, 世界" @@ -153,9 +180,14 @@ means exactly the same as var str string = "Hello, 世界" -One way to think about untyped constants is that they live in a kind of ideal space of values, a space less restrictive than Go's full type system. -But to do anything with them, we need to assign them to variables, and when that happens the _variable_ (not the constant itself) needs a type, and the constant can tell the variable what type it should have. -In this example, `str` becomes a value of type `string` because the untyped string constant gives the declaration its default type, `string`. +One way to think about untyped constants is that they live in a kind of +ideal space of values, +a space less restrictive than Go's full type system. +But to do anything with them, we need to assign them to variables, +and when that happens the _variable_ (not the constant itself) needs a type, +and the constant can tell the variable what type it should have. +In this example, `str` becomes a value of type `string` because the untyped +string constant gives the declaration its default type, `string`. In such a declaration, a variable is declared with a type and initial value. Sometimes when we use a constant, however, the destination of the value is not so clear. @@ -172,7 +204,8 @@ What happens when `fmt.Printf` is called with an untyped constant is that an int to pass as an argument, and the concrete type stored for that argument is the default type of the constant. This process is analogous to what we saw earlier when declaring an initialized value using an untyped string constant. -You can see the result in this example, which uses the format `%v` to print the value and `%T` to print the type of the value being passed to `fmt.Printf`: +You can see the result in this example, which uses the format `%v` to print +the value and `%T` to print the type of the value being passed to `fmt.Printf`: .play -edit constants/default2.go /START/,/STOP/ @@ -180,10 +213,12 @@ If the constant has a type, that goes into the interface, as this example shows: .play -edit constants/default3.go /START/,/STOP/ -(For more information about how interface values work, see the first sections of [[https://blog.golang.org/laws-of-reflection][this blog post]].) +(For more information about how interface values work, +see the first sections of [[https://blog.golang.org/laws-of-reflection][this blog post]].) In summary, a typed constant obeys all the rules of typed values in Go. -On the other hand, an untyped constant does not carry a Go type in the same way and can be mixed and matched more freely. +On the other hand, an untyped constant does not carry a Go type in the same +way and can be mixed and matched more freely. It does, however, have a default type that is exposed when, and only when, no other type information is available. * Default type determined by syntax @@ -191,7 +226,9 @@ It does, however, have a default type that is exposed when, and only when, no ot The default type of an untyped constant is determined by its syntax. For string constants, the only possible implicit type is `string`. For [[https://golang.org/ref/spec#Numeric_types][numeric constants]], the implicit type has more variety. -Integer constants default to `int`, floating-point constants `float64`, rune constants to `rune` (an alias for `int32`), and imaginary constants to `complex128`. +Integer constants default to `int`, floating-point constants `float64`, +rune constants to `rune` (an alias for `int32`), +and imaginary constants to `complex128`. Here's our canonical print statement used repeatedly to show the default types in action: .play -edit constants/syntax.go /START/,/STOP/ @@ -235,7 +272,8 @@ We can declare a constant with a very large value: .play -edit constants/float3.go /Println/ The error is, "constant 1.00000e+1000 overflows float64", which is true. -But `Huge` might be useful: we can use it in expressions with other constants and use the value of those expressions if the result +But `Huge` might be useful: we can use it in expressions with other constants +and use the value of those expressions if the result can be represented in the range of a `float64`. The statement, @@ -243,24 +281,29 @@ The statement, prints `10`, as one would expect. -In a related way, floating-point constants may have very high precision, so that arithmetic involving them is more accurate. +In a related way, floating-point constants may have very high precision, +so that arithmetic involving them is more accurate. The constants defined in the [[https://golang.org/pkg/math][math]] package are given with many more digits than are available in a `float64`. Here is the definition of `math.Pi`: Pi = 3.14159265358979323846264338327950288419716939937510582097494459 -When that value is assigned to a variable, some of the precision will be lost; the assignment will create the `float64` (or `float32`) +When that value is assigned to a variable, +some of the precision will be lost; +the assignment will create the `float64` (or `float32`) value closest to the high-precision value. This snippet .play -edit constants/float5.go /START/,/STOP/ prints `3.141592653589793`. -Having so many digits available means that calculations like `Pi/2` or other more intricate evaluations can carry more precision +Having so many digits available means that calculations like `Pi/2` or other +more intricate evaluations can carry more precision until the result is assigned, making calculations involving constants easier to write without losing precision. It also means that there is no occasion in which the floating-point corner cases like infinities, soft underflows, and `NaNs` arise in constant expressions. -(Division by a constant zero is a compile-time error, and when everything is a number there's no such thing as "not a number".) +(Division by a constant zero is a compile-time error, +and when everything is a number there's no such thing as "not a number".) * Complex numbers @@ -271,7 +314,8 @@ Here's a version of our now-familiar litany translated into complex numbers: The default type of a complex number is `complex128`, the larger-precision version composed of two `float64` values. -For clarity in our example, we wrote out the full expression `(0.0+1.0i)`, but this value can be shortened to `0.0+1.0i`, +For clarity in our example, we wrote out the full expression `(0.0+1.0i)`, +but this value can be shortened to `0.0+1.0i`, `1.0i` or even `1i`. Let's play a trick. @@ -288,7 +332,8 @@ Therefore, if we use it to declare a variable, the default type will be `complex .play -edit constants/complex2.go /START/,/STOP/ prints `complex128:` `(2+0i)`. -But numerically, `Two` can be stored in a scalar floating-point number, a `float64` or `float32`, with no loss of information. +But numerically, `Two` can be stored in a scalar floating-point number, +a `float64` or `float32`, with no loss of information. Thus we can assign `Two` to a `float64`, either in an initialization or an assignment, without problems: .play -edit constants/complex3.go /START/,/STOP/ @@ -300,7 +345,8 @@ This ability for a constant to "cross" types like this will prove useful. * Integers At last we come to integers. -They have more moving parts—[[https://golang.org/ref/spec#Numeric_types][many sizes, signed or unsigned, and more]]—but they play by the same rules. +They have more moving parts—[[https://golang.org/ref/spec#Numeric_types][many sizes, signed or unsigned, and more]]—but +they play by the same rules. For the last time, here is our familiar example, using just `int` this time: .play -edit constants/int1.go /START/,/STOP/ @@ -312,27 +358,36 @@ The same example could be built for any of the integer types, which are: uintptr (plus the aliases `byte` for `uint8` and `rune` for `int32`). -That's a lot, but the pattern in the way constants work should be familiar enough by now that you can see how things will play out. +That's a lot, but the pattern in the way constants work should be familiar +enough by now that you can see how things will play out. -As mentioned above, integers come in a couple of forms and each form has its own default type: `int` for simple constants like `123` or `0xFF` or `-14` +As mentioned above, integers come in a couple of forms and each form has +its own default type: +`int` for simple constants like `123` or `0xFF` or `-14` and `rune` for quoted characters like 'a', '世' or '\r'. No constant form has as its default type an unsigned integer type. -However, the flexibility of untyped constants means we can initialize unsigned integer variables using simple constants as long as we are clear about the type. +However, the flexibility of untyped constants means we can initialize unsigned +integer variables using simple constants as long as we are clear about the type. It's analogous to how we can initialize a `float64` using a complex number with zero imaginary part. -Here are several different ways to initialize a `uint`; all are equivalent, but all must mention the type explicitly for the result to be unsigned. +Here are several different ways to initialize a `uint`; +all are equivalent, but all must mention the type explicitly for the result to be unsigned. var u uint = 17 var u = uint(17) u := uint(17) -Similarly to the range issue mentioned in the section on floating-point values, not all integer values can fit in all integer types. -There are two problems that might arise: the value might be too large, or it might be a negative value being assigned to an unsigned integer type. -For instance, `int8` has range -128 through 127, so constants outside of that range can never be assigned to a variable of type `int8`: +Similarly to the range issue mentioned in the section on floating-point values, +not all integer values can fit in all integer types. +There are two problems that might arise: the value might be too large, +or it might be a negative value being assigned to an unsigned integer type. +For instance, `int8` has range -128 through 127, +so constants outside of that range can never be assigned to a variable of type `int8`: .play -edit constants/int2.go /var/ -Similarly, `uint8`, also known as `byte`, has range 0 through 255, so a large or negative constant cannot be assigned to a `uint8`: +Similarly, `uint8`, also known as `byte`, +has range 0 through 255, so a large or negative constant cannot be assigned to a `uint8`: .play -edit constants/int3.go /var/ @@ -362,7 +417,8 @@ We therefore might think we could write .play -edit constants/exercise1.go /const/ -but that is illegal because -1 cannot be represented by an unsigned variable; `-1` is not in the range of unsigned values. +but that is illegal because -1 cannot be represented by an unsigned variable; +`-1` is not in the range of unsigned values. A conversion won't help either, for the same reason: .play -edit constants/exercise2.go /const/ @@ -373,11 +429,13 @@ That is to say, this works: .play -edit constants/exercise3.go /START/,/STOP/ -but only because `v` is a variable; if we made `v` a constant, even an untyped constant, we'd be back in forbidden territory: +but only because `v` is a variable; if we made `v` a constant, +even an untyped constant, we'd be back in forbidden territory: .play -edit constants/exercise4.go /START/,/STOP/ -We return to our previous approach, but instead of `-1` we try `^0`, the bitwise negation of an arbitrary number of zero bits. +We return to our previous approach, but instead of `-1` we try `^0`, +the bitwise negation of an arbitrary number of zero bits. But that fails too, for a similar reason: In the space of numeric values, `^0` represents an infinite number of ones, so we lose information if we assign that to any fixed-size integer: @@ -401,13 +459,17 @@ Whatever the number of bits it takes to represent a `uint` in the current execut (on the [[https://blog.golang.org/playground][playground]], it's 32), this constant correctly represents the largest value a variable of type `uint` can hold. -If you understand the analysis that got us to this result, you understand all the important points about constants in Go. +If you understand the analysis that got us to this result, +you understand all the important points about constants in Go. * Numbers -The concept of untyped constants in Go means that all the numeric constants, whether integer, floating-point, complex, or even character values, +The concept of untyped constants in Go means that all the numeric constants, +whether integer, floating-point, complex, +or even character values, live in a kind of unified space. -It's when we bring them to the computational world of variables, assignments, and operations that the actual types matter. +It's when we bring them to the computational world of variables, +assignments, and operations that the actual types matter. But as long as we stay in the world of numeric constants, we can mix and match values as we like. All these constants have numeric value 1: @@ -419,7 +481,8 @@ All these constants have numeric value 1: 'b' - 'a' 1.0+3i-3.0i -Therefore, although they have different implicit default types, written as untyped constants they can be assigned to a variable of any integer type: +Therefore, although they have different implicit default types, +written as untyped constants they can be assigned to a variable of any integer type: .play -edit constants/numbers1.go /START/,/STOP/ @@ -432,7 +495,8 @@ You can even do nutty stuff like which yields 145.5, which is pointless except to prove a point. But the real point of these rules is flexibility. -That flexibility means that, despite the fact that in Go it is illegal in the same expression to mix floating-point and integer variables, +That flexibility means that, despite the fact that in Go it is illegal in +the same expression to mix floating-point and integer variables, or even `int` and `int32` variables, it is fine to write sqrt2 := math.Sqrt(2) |