2015-02-20 1 views
1

Рассмотрим этот код:Почему golang запрещает присвоение одному и тому же базовому типу, если он является родным?

package main 
import "fmt" 

type specialString string 

func printString(s string) { 
    fmt.Println(s) 
} 

// unlike, say, C++, this is not legal GO, because it redeclares printString 
//func printString(s specialString) {  
// fmt.Println("Special: " + s) 
//} 

func main() { 
    ss := specialString("cheese") 
    // ... so then why shouldn't this be allowed? 
    printString(ss) 
} 

Мой вопрос: почему это язык определяется так, что вызов printString(ss) в main() не допускается? (Я не ищу ответы, которые указывают на правила Golang при назначении, я их уже прочитал, и я вижу, что как specialString, так и строка имеют один и тот же «базовый тип», и оба типа называются «named» - если вы считаете типичный тип «строка», который, по-видимому, делает Голанг, и поэтому они не могут быть назначены по правилам.)

Но Почему - это правила? Какую проблему решают, рассматривая встроенные типы как «именованные» типы и не позволяя вам передавать именованные типы ко всем стандартным библиотечным функциям, которые принимают тот же базовый встроенный тип? Кто-нибудь знает, о чем здесь говорили разработчики языка?

С моей точки зрения, это, кажется, создает много преобразования бессмысленного типа в коде, и отпугивает использование сильной типизации, когда он на самом деле будет иметь смысл ..

+6

Только по соображениям безопасности. Сильная строгая типизация делает ошибки менее вероятными. В вашем примере, где вы используете «специальную строку», подразумевается, что это строка, и можно получить впечатление от подтипирования, которое Go не имеет. Вы вводите строку типа foobar для foobar (которая фактически является строкой), но почему имеет смысл напечатать foobar с помощью функции printstring? Foobar не 'string', это foobar. Если простая строка - это все, что вам нужно, тогда нет необходимости вводить «специальную строку», просто используйте обычную строку. – Volker

+3

Также вы найдете еще несколько аргументов в официальном FAQ: [Почему Go не предоставляет неявные числовые преобразования?] (Http://golang.org/doc/faq#conversions) – icza

+2

Также рассмотрим дизайн частей библиотеки, таких как ' time.Duration'. Язык запрещает «int64's» обрабатывать как «time.Duration» без учета элементов expliclt. Функция, которую вы критикуете, может (и есть!) Использоваться для кодирования правила «тех же единиц», которое мы знаем из средней школы. При использовании хорошо, это должно затруднить совершение тех же самых ошибок, которые посылают спутники с неба из-за ошибки преобразования метрики. – dyoo

ответ

3

Я считаю, что логика исходных авторов здесь, что именованный тип назван по причине - он представляет собой нечто иное, а не только базовый тип.

Я думаю, что я читал его где-то в голангах, но не могу вспомнить точное обсуждение.

Рассмотрим следующий пример:

type Email string 

Вы назвали его электронную почту, потому что вы должны представлять по электронной почте объект, и «строка» просто упростили ее представление, достаточное для самого начала. Но позже, вы можете захотеть изменить электронную почту к чему-то более сложному, как:

type Email struct { 
    Address string 
    Name string 
    Surname string 
} 

И это нарушит весь код, который работает с электронной почтой неявно предполагая, что это строка.

2

Это происходит потому, что Go делает не имеют наследования классов. Вместо этого он использует структуру структуры. Именованные типы не наследуют свойства от их базового типа (поэтому его не называют «базовым типом»).

Поэтому, когда вы объявляете именованный тип specialString с базовым типом предопределенного типа string, ваш новый тип совершенно другого типа из базового. Это связано с тем, что Go предполагает, что вы захотите присвоить новый тип поведения новому типу и не будете проверять его базовый тип до времени выполнения. Вот почему Go является как статическим, так и динамическим языком.

При печати

fmt.Println(reflect.TypeOf(ss))  // specialString 

Вы specialString, не string. Если вы посмотрите на Println() определение выглядит следующим образом:

func Println(a ...interface{}) (n int, err error) { 
     return Fprintln(os.Stdout, a...) 
} 

Это означает, что вы можете печатать любые предопределенные типы (INT, float64, строка), так как все они реализуют по крайней мере нулевую методу, что делает их уже соответствуют пустому интерфейсу и передаются как «печатные», но не ваш именованный тип specialString, который остается неизвестным для Go во время компиляции. Мы можем проверить, напечатав тип нашего interface{} против specialString.

type specialString string 
type anything interface{} 

s := string("cheese") 
ss := specialString("special cheese") 
at := anything("any cheese") 

fmt.Println(reflect.TypeOf(ss))  // specialString 
fmt.Println(reflect.TypeOf(s))  // string 
fmt.Println(reflect.TypeOf(at))  // Wow, this is also string! 

Вы можете видеть, что specialString продолжает шалить свою идентичность. Теперь посмотрим, как это происходит при передаче в функцию во время выполнения

func printAnything(i interface{}) { 
     fmt.Println(i) 
} 

fmt.Println(ss.(interface{}))  // Compile error! ss isn't interface{} but 
printAnything(ss)     // prints "special cheese" alright 

ss стал проходимый, как interface{} к функции. К тому времени Go уже сделал ss a interface{}.

Если вы действительно хотите понять глубину вытяжки, это article on interfaces действительно бесценно.

+0

Я нашел этот ответ информативным, и поэтому я проголосовал за него, но в конечном итоге я выбрал ответ Дивана, потому что его ответ был более непосредственно направлен на суть моего вопроса: «Какая проблема это решает?» - Я действительно искал «почему языковые дизайнеры считают, что это поведение было хорошей идеей», а не «какие аспекты реализации языка заставляют его работать таким образом?» - Тем не менее, я благодарю вас за то, что вы нашли время для объяснения в таких замечательных деталях. – JVMATL

+0

@ JVMATL Спасибо! – PieOhPah

Смежные вопросы