Arithmetic operators

Arithmetic operators Arithmetic operators apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, -, *, /) apply to integer, floating-point, and complex types; + also applies to strings. The bitwise logical and shift operators apply to integers only. + sum integers, floats, complex values, strings - difference integers, floats, complex values * product integers, floats, complex values / quotient integers, floats, complex values % remainder integers & bitwise AND integers | bitwise OR integers ^ bitwise XOR integers &^ bit clear (AND NOT) integers << left shift integer << integer >= 0 >> right shift integer >> integer >= 0 There should be nothing surprising here.


Operator precedence

I hope we all know what operator precedence means… but just in case it’s fuzzy, I’ll illustrate with a simple example from junior high school. What does this mean? 1 + 2 * 3 It’s either 9 or 7, right? It depends on the order in which we apply the + and * operations. I’m sure most of us agree that the correct answer is actually 7, because multiplication takes precedence over addition.


Shift operators

Operators … The right operand in a shift expression must have integer type or be an untyped constant representable by a value of type uint. A bunch of examples demonstrating this follow in the spec, so I won’t provide my own. … If the left operand of a non-constant shift expression is an untyped constant, it is first implicitly converted to the type it would assume if the shift expression were replaced by its left operand alone.


Operators

Operators Operators combine operands into expressions. Nice. I love a concise description/definition. Especially after the confusion that was yesterday’s post about loose and exact unification. Expression = UnaryExpr | Expression binary_op Expression . UnaryExpr = PrimaryExpr | unary_op UnaryExpr . binary_op = "||" | "&&" | rel_op | add_op | mul_op . rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" . add_op = "+" | "-" | "|" | "^" .


Exact & loose type unification

Type unification … Unification uses a combination of exact and loose unification depending on whether two types have to be identical, assignment-compatible, or only structurally equal. The respective type unification rules are spelled out in detail in the Appendix. The precise definitions of “exact” and “loose” unification are buried in the appendix, and depend on the specific types involved. In general, I think it’s not terribly inaccurate to say that exact unification applies when the two types are identical, for composite types with identical structure (i.


Type unification

A couple of days ago we saw the spec reference the concept of “type unification”. Today we start through that explanation…. Type unification Type inference solves type equations through type unification. Type unification recursively compares the LHS and RHS types of an equation, where either or both types may be or contain bound type parameters, and looks for type arguments for those type parameters such that the LHS and RHS match (become identical or assignment-compatible, depending on context).


Successful type inference

Today we finish up the discussion on type inference. So we’ve now Type inference … If the two phases are successful, type inference determined a type argument for each bound type parameter: Pk ➞ Ak A type argument Ak may be a composite type, containing other bound type parameters Pk as element types (or even be just another bound type parameter). In a process of repeated simplification, the bound type parameters in each type argument are substituted with the respective type arguments for those type parameters until each type argument is free of bound type parameters.

Subscribe to Boldly Go: Daily

Every day I'll send you advice to improve your understanding of Go. Don't miss out! I will respect your inbox, and honor my privacy policy.

Unsure? Browse the archive.


Precedence of type inference

Type inference … Type inference gives precedence to type information obtained from typed operands before considering untyped constants. Therefore, inference proceeds in two phases: The type equations are solved for the bound type parameters using type unification. If unification fails, type inference fails. Type unification comes up next in the spec, so we’ll learn exactly what that means soon. For each bound type parameter Pk for which no type argument has been inferred yet and for which one or more pairs (cj, Pk) with that same type parameter were collected, determine the constant kind of the constants cj in all those pairs the same way as for constant expressions.


The many type equations

Type inference … Type inference supports calls of generic functions and assignments of generic functions to (explicitly function-typed) variables. So just to call out this point, made in passing: You cannot assign the result of a generic function to a generic, non-instantiated type, even if inferrence should be intuitive. Do demonstrate: type S[T ~int | ~float64] struct { Value T } func sum[V ~int | ~float32](a, b V) V { return a + b } // cannot use generic type S[T ~int | ~float64] without instantiation x := S{ Value: sum(int(1), int(2)), } Even though the type returned from sum() is obviously int, we cannot do this assignment.


Bound type parameters

Type inference … Given a set of type equations, the type parameters to solve for are the type parameters of the functions that need to be instantiated and for which no explicit type arguments is provided. These type parameters are called bound type parameters. For instance, in the dedup example above, the type parameters P and E are bound to dedup. An argument to a generic function call may be a generic function itself.

How-Tos

17 min read


Error handling in Go web apps shouldn't be so awkward

A useful error handling pattern for Go REST, gRPC, and other services.