question about types

684 views
Skip to first unread message

John Marshall

unread,
Dec 9, 2010, 8:24:01 PM12/9/10
to golan...@googlegroups.com
Hi,

In the program below, can someone explain why the compiler treats Int and int as non-interchangeable but
map[interface{}]interface{} and Map as interchangeable?

Note, when lines 42 and 43 are uncommented, this is what the compiler gives:
test2.go:42: cannot use i2 (type Int) as type int in function argument
test2.go:43: cannot use i (type int) as type Int in function argument

I am using the latest 8g compiler.

Thanks,
John

-----

//

package main

import (
"fmt"
"reflect"
)

type Int int
type Map map[interface{}]interface{}

func Printint(i int) {
fmt.Printf("int (%s) (%v)\n", i, i)
}

func PrintInt(i Int) {
fmt.Printf("Int (%s) (%v)\n", i, i)
}

func Printmap(m map[interface{}]interface{}) {
fmt.Printf("map (%v)\n", reflect.Typeof(m))
for k, v := range m {
fmt.Printf("k (%v) v (%v)\n", k, v)
}
}

func PrintMap(m Map) {
fmt.Printf("Map (%v)\n", reflect.Typeof(m))
for k, v := range m {
fmt.Printf("k (%v) v (%v)\n", k, v)
}
}

func main() {
i := 1
i2 := Int(2)

Printint(i)
PrintInt(i2)

//Printint(i2) // line 42: won't compile
//PrintInt(i) // line 43: won't compile

fmt.Printf("-----\n")

m := map[interface{}]interface{} {
"x": 1,
}

m2 := Map {
"x": 2,
}

fmt.Printf("m (%v)\n", reflect.Typeof(m))
fmt.Printf("m2 (%v)\n", reflect.Typeof(m2))

Printmap(m)
PrintMap(m2)

Printmap(m2)
PrintMap(m)
}


Rob 'Commander' Pike

unread,
Dec 9, 2010, 9:22:59 PM12/9/10
to John Marshall, golan...@googlegroups.com
It's all in the spec. Int and int are both named types, so they are
different types. Map is a named type but map[interface{}]interface{}
is not; since their separate declarations are identical they can be
assigned between.

Here's a much simpler example showing the effect; since Pint and *int
are equivalent but only one is named. This compiles without incident.

package main

func main() {
type Pint *int
var p *int
var P Pint
p = P
_ = p
}


-rob

John Marshall

unread,
Dec 9, 2010, 10:02:41 PM12/9/10
to golan...@googlegroups.com
On 12/09/2010 09:22 PM, Rob 'Commander' Pike wrote:
> It's all in the spec.
Sorry. I was looking everywhere but.

> Int and int are both named types, so they are
> different types. Map is a named type but map[interface{}]interface{}
> is not; since their separate declarations are identical they can be
> assigned between.
>
> Here's a much simpler example showing the effect; since Pint and *int
> are equivalent but only one is named. This compiles without incident.
Having read the spec, I've another question. So in your example, if I did something
like:
type Xint Pint

Pint and Xint are named types with *int as the underlying type (right?). Now Xint
and Pint will be treated as *int and can be passed to "func x(*int)", but Xint will
_not_ be treated as Pint, and cannot be passed to "func y(Pint)", is that correct?
If this is true, can you give an explanation (or point me to the doc) on why this is
so (I suspect my misunderstanding is b/c of coming from OO).

Thanks,
John

Rob 'Commander' Pike

unread,
Dec 9, 2010, 10:13:03 PM12/9/10
to John Marshall, golan...@googlegroups.com
That's correct.

The reasoning goes like this: If you take the trouble to name Pint and
Xint, it's because you want them to be distinct. (This isn't C, where
a typedef is just an alias.) But sometimes it makes sense to speak of
the structure as all you care about; consider things like the indexing
functions in the bytes package. So we allow assignment in those
cases.

The motivating example in our thinking was something like type Point
struct { X, Y float }. Another named type with the same fields
probably is a different idea or it would share the declaration; they
shouldn't be interchangeable. Another way to say it is that if they
don't have the same methods (even potentially), they shouldn't be
assignable. On the other hand a generic struct { X, Y int } is
talking just about the structure, not its interpretation, so it makes
sense to allow assignment between that type and Point.

Also, although I cannot reconstruct the history, the current rules
were arrived at largely through experience coupled with a desire for a
simple specification. They aren't arbitrary.

-rob

jimmy frasche

unread,
Dec 9, 2010, 10:45:54 PM12/9/10
to Rob 'Commander' Pike, John Marshall, golan...@googlegroups.com
I've never had a problem with this aspect of the type system but I've
always felt a bit uneasy about this distinction in a way I couldn't
articulate. After reading the last e-mail, I feel quite enlightened,
however. Thanks for the lucid explanation, Rob. And thank you for
asking the question, John.

John Marshall

unread,
Dec 9, 2010, 11:55:14 PM12/9/10
to golan...@googlegroups.com
Thanks. Just what I was looking for.

So, other than explaining how types work, is there much of a use case
for the T2 in the examples in the spec?

type T1 string
type T2 T1

It would seem that "type T2 T1" actually hides (e.g., if it was in a
separate file) the fact that the underlying type of T2 is string and could
only be used with functions dealing with string (and those of T2).

Thanks again,
John

Steven

unread,
Dec 10, 2010, 12:28:36 AM12/10/10
to John Marshall, golan...@googlegroups.com
On Thursday, December 9, 2010, John Marshall <John.M...@ec.gc.ca> wrote:
> Thanks. Just what I was looking for.
>
> So, other than explaining how types work, is there much of a use case
> for the T2 in the examples in the spec?
>
>   type T1 string
>   type T2 T1
>
> It would seem that "type T2 T1" actually hides (e.g., if it was in a
> separate file) the fact that the underlying type of T2 is string and could
> only be used with functions dealing with string (and those of T2).
>
> Thanks again,
> John
>

string is another named type so you couldn't pass a T1 where a string
is expected. The use in the spec is for illustration purposes. The
purpose of such a declaration could be to ensure the two types T1 and
T2 are represented the same even if that representation changes, so
they will always be conversion compatible. This makes more sense in
the more complex case of a struct type, where it's not only more
likely for the definition to change, but this type of transitory
declaration reduces code duplication and makes the relationship clear,
and it may not even be possible to declare the two types independently
of each other and still have them be conversion compatible (if the
types are declared in separate packages and have private fields).

John Marshall

unread,
Dec 10, 2010, 8:17:20 AM12/10/10
to golan...@googlegroups.com
On 12/10/2010 12:28 AM, Steven wrote:
> On Thursday, December 9, 2010, John Marshall<John.M...@ec.gc.ca> wrote:
>> Thanks. Just what I was looking for.
>>
>> So, other than explaining how types work, is there much of a use case
>> for the T2 in the examples in the spec?
>>
>> type T1 string
>> type T2 T1
>>
>> It would seem that "type T2 T1" actually hides (e.g., if it was in a
>> separate file) the fact that the underlying type of T2 is string and could
>> only be used with functions dealing with string (and those of T2).
>>
>> Thanks again,
>> John
>>
> string is another named type so you couldn't pass a T1 where a string
> is expected.
Oops. I knew that :)

> The use in the spec is for illustration purposes. The
> purpose of such a declaration could be to ensure the two types T1 and
> T2 are represented the same even if that representation changes, so
> they will always be conversion compatible. This makes more sense in
> the more complex case of a struct type, where it's not only more
> likely for the definition to change, but this type of transitory
> declaration reduces code duplication and makes the relationship clear,
> and it may not even be possible to declare the two types independently
> of each other and still have them be conversion compatible (if the
> types are declared in separate packages and have private fields).
Thanks Rob and Steven.

John


Reply all
Reply to author
Forward
0 new messages