الكتاب الأساسي

تفاصيل Go 101

الفهرس:

يمكن استيراد الحزمة نفسها أكثر من مرة في ملف مصدري واحد.

يمكن لملف Go مصدري أن يستورد الحزمة نفسها عدة مرات، لكن يجب أن تكون أسماء الاستيراد مختلفة. وتشير عمليات استيراد الحزمة نفسها هذه إلى نسخة الحزمة ذاتها.

مثلاً:


package main

import "fmt"
import "io"
import inout "io"

func main() {
	fmt.Println(&inout.EOF == &io.EOF) // true
}

يمكن وضع فرع default في كتلتي switch وselect قبل جميع فروع case أو بعد جميع فروع case أو بين فروع case.

مثلاً:


	switch n := rand.Intn(3); n {
	case 0: fmt.Println("n == 0")
	case 1: fmt.Println("n == 1")
	default: fmt.Println("n == 2")
	}

	switch n := rand.Intn(3); n {
	default: fmt.Println("n == 2")
	case 0: fmt.Println("n == 0")
	case 1: fmt.Println("n == 1")
	}

	switch n := rand.Intn(3); n {
	case 0: fmt.Println("n == 0")
	default: fmt.Println("n == 2")
	case 1: fmt.Println("n == 1")
	}

	var x, y chan int

	select {
	case <-x:
	case y <- 1:
	default:
	}

	select {
	case <-x:
	default:
	case y <- 1:
	}

	select {
	default:
	case <-x:
	case y <- 1:
	}

لا يمكن تكرار تعبيرات الحالات الثابتة العددية في كتلة switch، لكن يمكن تكرار التعبيرات المنطقية.

مثلاً، يفشل البرنامج التالي في التصريف.


package main

func main() {
	switch 123 {
	case 123:
	case 123: // خطأ: حالة مكررة
	}
}

لكن البرنامج التالي يُصرَّف بنجاح.


package main

func main() {
	switch false {
	case false:
	case false:
	}
}

للاطلاع على الأسباب، يرجى قراءة هذه المسألة. هذا السلوك يعتمد على المصرِّف. في الواقع، لا يسمح مصرِّف Go القياسي أيضاً بتكرار تعبيرات حالات النصوص، لكن gccgo يسمح بذلك.

تُقيَّم تعبيرات switch في كتلة switch دائماً كقيم مُنمَّطة.

مثلاً، يُنظر إلى تعبير switch وهو 123 في كتلة switch التالية كقيمة من النوع int بدلاً من عدد صحيح غير مُنمَّط. لذا يفشل البرنامج التالي في التصريف.


package main

func main() {
	switch 123 {
	case int64(123):  // خطأ: أنواع غير متوافقة
	case uint32(789): // خطأ: أنواع غير متوافقة
	}
}

تعبير switch الافتراضي في كتلة switch هو القيمة المُنمَّطة true من النوع bool المُعرَّف مسبقاً.

مثلاً، سيطبع البرنامج التالي true.


package main

import "fmt"

func main() {
	switch { // <=> switch true {
	case true:  fmt.Println("true")
	case false: fmt.Println("false")
	}
}

أحياناً، يمكن وضع القوس المفتوح { لكتلة شيفرة صريحة في السطر التالي.

مثلاً:


package main

func main() {
	var i = 0
Outer:
	for
	{ %% لا بأس به في السطر التالي
		switch
		{ %% لا بأس به في السطر التالي
		case i == 5:
			break Outer
		default:
			i++
		}
	}
}

ما النتيجة التي سيطبعها البرنامج التالي؟ true أم false؟ الجواب هو true. يرجى قراءة قواعد فواصل الأسطر في Go للأسباب.


package main

import "fmt"

func False() bool {
	return false
}

func main() {
	switch False()
	{
	case true:  fmt.Println("true")
	case false: fmt.Println("false")
	}
}

يجب أن تكون بعض كتل فروع case صريحة.

مثلاً، يفشل البرنامج التالي في التصريف.


func demo(n, m int) (r int) {
	switch n {
	case 123:
		if m > 0 {
			goto End
		}
		r++

		End: // خطأ صياغي: جملة مفقودة بعد التسمية
	default:
		r = 1
	}
	return
}

لكي يُصرَّف بنجاح، يجب أن تكون كتلة شيفرة فرع case صريحة:


func demo(n, m int) (r int) {
	switch n {
	case 123: {
		if m > 0 {
			goto End
		}
		r++

		End:
	}
	default:
		r = 1
	}
	return
}

بدلاً من ذلك، يمكننا إضافة فاصلة منقوطة بعد تعريف التسمية End::


func demo(n, m int) (r int) {
	switch n {
	case 123:
		if m > 0 {
			goto End
		}
		r++

		End:;
	default:
		r = 1
	}
	return
}

يرجى قراءة قواعد فواصل الأسطر في Go للأسباب.

يمكن لنداءات الدوال المؤجَّلة المتداخلة تعديل قيم نتائج الإرجاع لأعمق دالة محتوية لها.

مثلاً:


package main

import "fmt"

func F() (r int) {
	defer func() {
		r = 789
	}()

	return 123 // <=> r = 123; return
}

func main() {
	fmt.Println(F()) // 789
}

قد تكون بعض نداءات recover بلا أثر.

ينبغي أن نستدعي الدالة recover في الأماكن الصحيحة. يرجى قراءة الأماكن الصحيحة لاستدعاء الدالة المدمجة recover للتفاصيل.

إنهاء برنامج بنداء الدالة os.Exit وإنهاء goroutine بنداء الدالة runtime.Goexit.

يمكننا إنهاء برنامج من أي دالة بنداء الدالة os.Exit. يأخذ نداء الدالة os.Exit رمزاً من النوع int كمعطى ويعيد الرمز إلى نظام التشغيل.

مثال:


// exit-example.go
package main

import "os"
import "time"

func main() {
	go func() {
		time.Sleep(time.Second)
		os.Exit(1)
	}()
	select{}
}

شغّله:


$ go run a.go
exit status 1
$ echo $?
1

يمكننا إنهاء goroutine بنداء الدالة runtime.Goexit. لا تملك الدالة runtime.Goexit أي وسائط.

في المثال التالي، لن تُطبع كلمة Java.


package main

import "fmt"
import "runtime"

func main() {
	c := make(chan int)
	go func() {
		defer func() {c <- 1}()
		defer fmt.Println("Go")
		func() {
			defer fmt.Println("C")
			runtime.Goexit()
		}()
		fmt.Println("Java")
	}()
	<-c
}

أسبقية معامل الزيادة ++ ومعامل النقصان -- أدنى من أسبقية معامل إلغاء الإشارة * ومعامل أخذ العنوان &، وهما أدنى من أسبقية معامل اختيار الخاصية . في المحدِّدات.

مثلاً:


package main

import "fmt"

type T struct {
	x int
	y *int
}

func main() {
	var t T
	p := &t.x // <=> p := &(t.x)
	fmt.Printf("%T\n", p) // *int

	*p++ // <=> (*p)++
	*p-- // <=> (*p)--

	t.y = p
	a := *t.y // <=> *(t.y)
	fmt.Printf("%T\n", a) // int
}

تعتمد قاعدة استنتاج النوع للمعامَل به الأيسر غير المُنمَّط في عملية إزاحة البتات على ما إذا كان المعامَل به الأيمن ثابتاً أم لا.


package main

func main() {
}

const M  = 2
// يُصرَّف بنجاح. يُستنتج أن 1.0 قيمة من النوع int.
var _ = 1.0 << M

var N = 2
// يفشل في التصريف. يُستنتج أن 1.0 قيمة من النوع float64.
var _ = 1.0 << N

يرجى قراءة هذا المقال للأسباب.

يمكن تحويل قيم نوعَي مؤشر مختلفَي النوعين الكامنين إلى بعضهما بعضاً إذا كان النوعان القاعديان لنوعيهما الكامنين يتشاركان النوع الكامن نفسه.

مثال:


package main

type MyInt int64
type Ta    *int64
type Tb    *MyInt

func main() {
	var a Ta
	var b Tb

	// التحويل المباشر غير مسموح به.
	//a = Ta(b) // خطأ

	// لكن التحويل غير المباشر ممكن.
	y := (*MyInt)(b)
	x := (*int64)(y)
	a = x           // <=> السطر التالي
	a = (*int64)(y) // <=> السطر التالي
	a = (*int64)((*MyInt)(b))
	_ = a
}

قد تكون عناوين قيم مختلفة صفرية الحجم متساوية أو غير متساوية.

ما إذا كان عنوانا قيمتين صفريتي الحجم متساويين أم لا يعتمد على المصرِّف وإصدار المصرِّف.


package main

import "fmt"

func main() {
	a := struct{}{}
	b := struct{}{}
	x := struct{}{}
	y := struct{}{}
	m := [10]struct{}{}
	n := [10]struct{}{}
	o := [10]struct{}{}
	p := [10]struct{}{}

	fmt.Println(&x, &y, &o, &p)

	// بالنسبة لمصرِّف Go القياسي (1.25.n)،
	// تهرب x و y و o و p إلى الكومة، لكن
	// تُخصَّص a و b و m و n على المكدس.

	fmt.Println(&a == &b) // false
	fmt.Println(&x == &y) // true
	fmt.Println(&a == &x) // false

	fmt.Println(&m == &n) // false
	fmt.Println(&o == &p) // true
	fmt.Println(&n == &p) // false
}

المخرجات المشار إليها في الشيفرة أعلاه تخص مصرِّف Go القياسي 1.25.n.

قد يكون النوع القاعدي لنوع مؤشر هو نوع المؤشر نفسه.

مثال:


package main

func main() {
	type P *P
	var p P
	p = &p
	p = **************p
}

وبالمثل،

  • يمكن أن يكون نوع عنصر نوع شريحة هو نوع الشريحة نفسه،
  • ويمكن أن يكون نوع عنصر نوع خريطة هو نوع الخريطة نفسه،
  • ويمكن أن يكون نوع عنصر نوع قناة هو نوع القناة نفسه،
  • ويمكن أن يكون نوعا المعطى والنتيجة لنوع دالة هو نوع الدالة نفسه.

package main

func main() {
	type S []S
	type M map[string]M
	type C chan C
	type F func(F) F

	s := S{0:nil}
	s[0] = s
	m := M{"Go": nil}
	m["Go"] = m
	c := make(C, 3)
	c <- c; c <- c; c <- c
	var f F
	f = func(F)F {return f}

	_ = s[0][0][0][0][0][0][0][0]
	_ = m["Go"]["Go"]["Go"]["Go"]
	<-<-<-c
	f(f(f(f(f))))
}

تفصيل حول اختصارات المحدِّدات.

بالنسبة لقيمة مؤشر، سواء كان نوعها مسمّى أم لا، إذا كان النوع القاعدي لنوعها (نوع المؤشر) نوع بنية، فيمكننا عندئذٍ اختيار حقول قيمة البنية المشار إليها بواسطة قيمة المؤشر من خلال قيمة المؤشر. لكن إذا كان نوع قيمة المؤشر نوعاً مسمّى، فلا يمكننا اختيار طرق قيمة البنية المشار إليها بواسطة قيمة المؤشر من خلال قيمة المؤشر.


package main

type T struct {
	x int
}
func (T) m(){} // يملك T طريقة واحدة.

type P *T  // نوع مؤشر مسمّى بمستوى واحد.
type PP *P // نوع مؤشر مسمّى بمستويين.

func main() {
	var t T
	var tp = &t
	var tpp = &tp
	var p P = tp
	var pp PP = &p
	tp.x = 12  // صحيح
	p.x = 34   // صحيح
	pp.x = 56  // خطأ: النوع PP ليس له حقل أو طريقة x
	tpp.x = 78 // خطأ: النوع **T ليس له حقل أو طريقة x

	tp.m()  // صحيح. النوع *T يملك أيضاً طريقة "m".
	p.m()   // خطأ: النوع P ليس له حقل أو طريقة m
	pp.m()  // خطأ: النوع PP ليس له حقل أو طريقة m
	tpp.m() // خطأ: النوع **T ليس له حقل أو طريقة m
}

يمكن أحياناً تبسيط القيم الحرفية المركّبة المتداخلة.

يرجى قراءة يمكن تبسيط القيم الحرفية المركّبة المتداخلة للتفاصيل.

في بعض السيناريوهات، لا بأس في استخدام مؤشرات المصفوفات كمصفوفات.

يرجى قراءة استخدام مؤشرات المصفوفات كمصفوفات للتفاصيل.

استرجاع العناصر من خرائط nil لن يُنتج panic. النتيجة هي قيمة صفرية للعنصر.

مثلاً، الدالتان Foo1 وFoo2 متكافئتان، لكن الدالة Foo2 أنظف بكثير من الدالة Foo1.


func Foo1(m map[string]int) int {
	if m != nil {
		return m["foo"]
	}
	return 0
}

func Foo2(m map[string]int) int {
	return m["foo"]
}

حذف مدخل من خريطة nil لن يُنتج panic. إنها عملية بلا أثر.

مثلاً، البرنامج التالي لن يُنتج panic.


package main

func main() {
	var m map[string]int // nil
	delete(m, "foo")
}

قد تشترك شريحة النتيجة لنداء الدالة append في بعض العناصر مع الشريحة الأصلية أو لا تشترك.

يرجى قراءة إلحاق عناصر الحاويات وحذفها للتفاصيل.

قد يكون طول شريحة فرعية أكبر من الشريحة الأساسية التي اشتُقّت منها.

مثلاً،


package main

import "fmt"

func main() {
	s := make([]int, 3, 9)
	fmt.Println(len(s)) // 3
	s2 := s[2:7]
	fmt.Println(len(s2)) // 5
}

يرجى قراءة اشتقاق الشرائح من المصفوفات والشرائح للتفاصيل.

اشتقاق شريحة فرعية من شريحة nil لا بأس به إذا كانت جميع الفهارس المستخدمة في تعبير الشريحة الفرعية صفراً. وشريحة النتيجة تكون أيضاً شريحة nil.

مثلاً، البرنامج التالي لن يُنتج panic في وقت التشغيل.


package main

import "fmt"

func main() {
	var x []int // nil
	a := x[:]
	b := x[0:0]
	c := x[:0:0]
	// طباعة "true" ثلاث مرات.
	fmt.Println(a == nil, b == nil, c == nil)
}

يرجى قراءة اشتقاق الشرائح من المصفوفات والشرائح للتفاصيل.

المرور بـ range على خرائط nil أو شرائح nil لا بأس به، إنها عملية بلا أثر.

مثلاً، البرنامج التالي يُصرَّف بنجاح.


package main

func main() {
	var s []int // nil
	for range s {
	}

	var m map[string]int // nil
	for range m {
	}
}

المرور بـ range على مؤشر مصفوفة nil لا بأس به إذا جرى تجاهل متغير التكرار الثاني أو حذفه.

مثلاً، سيطبع البرنامج التالي 01234.


package main

import "fmt"

func main() {
	var a *[5]int // nil
	for i, _ := range a {
		fmt.Print(i)
	}
}

يمكن تعديل طول الشريحة وسعتها كلٌّ على حدة.

يمكننا تعديل طول الشريحة وسعتها كلٌّ على حدة عبر طريقة الانعكاس. يرجى قراءة تعديل خاصيتَي الطول والسعة لقيمة شريحة بشكل فردي للتفاصيل.

يجب أن تكون الفهارس في القيم الحرفية المركّبة للشرائح والمصفوفات ثوابت وغير سالبة.

مثلاً، تفشل الشيفرة التالية في التصريف.


var k = 1
// خطأ: يجب أن يكون الفهرس ثابتاً عددياً صحيحاً غير سالب
var x = [2]int{k: 1}
// خطأ: يجب أن يكون الفهرس ثابتاً عددياً صحيحاً غير سالب
var y = []int{k: 1}

لاحظ أن المفاتيح في القيم الحرفية المركّبة للخرائط غير مطلوب أن تكون ثوابت.

لا يمكن تكرار الفهارس أو المفاتيح الثابتة في القيم الحرفية المركّبة للشرائح/المصفوفات/الخرائط.

مثلاً، تفشل الشيفرة التالية في التصريف.


// خطأ: فهرس مكرر في القيمة الحرفية للمصفوفة: 1
var a = []bool{0: false, 1: true, 1: true}
// خطأ: فهرس مكرر في القيمة الحرفية للمصفوفة: 0
var b = [...]string{0: "foo", 1: "bar", 0: "foo"}
// خطأ: مفتاح مكرر "foo" في القيمة الحرفية للخريطة
var c = map[string]int{"foo": 1, "foo": 2}

يمكن استخدام هذه الميزة في تأكيد بعض الشروط في وقت التصريف.

عناصر المصفوفات غير القابلة للعنونة غير قابلة للعنونة أيضاً، لكن عناصر الشرائح غير القابلة للعنونة قابلة للعنونة دائماً.

السبب هو أن عناصر قيمة المصفوفة والمصفوفة ستُخزَّن في كتلة الذاكرة نفسها عندما تُخزَّن المصفوفة في الذاكرة. لكن الوضع مختلف بالنسبة للشرائح.

مثال:


package main

func main() {
	// القيم الحرفية المركّبة للحاويات غير قابلة للعنونة.

	// لا بأس في أخذ عناوين عناصر القيمة الحرفية للشريحة.
	_ = &[]int{1}[0] // صحيح
	// لا يمكن أخذ عناوين عناصر القيمة الحرفية للمصفوفة.
	_ = &[5]int{}[0] // خطأ

	// لا بأس في تعديل عناصر القيمة الحرفية للشريحة.
	[]int{1,2,3}[1] = 9  // صحيح
	// لا يمكن تعديل عناصر القيمة الحرفية للمصفوفة.
	[3]int{1,2,3}[1] = 9 // خطأ
}

لا بأس في اشتقاق شرائح فرعية من شرائح غير قابلة للعنونة، لكن لا يُسمح بذلك من مصفوفات غير قابلة للعنونة.

السبب هو نفسه كما في التفصيل السابق.

مثال:


package main

func main() {
	// عناصر الخرائط غير قابلة للعنونة في Go.

	// الأسطر التالية تُصرَّف بنجاح. اشتقاق
	// شرائح من شرائح غير قابلة للعنونة مسموح به.
	_ = []int{6, 7, 8, 9}[1:3]
	var ms = map[string][]int{"abc": {0, 1, 2, 3}}
	_ = ms["abc"][1:3]

	// الأسطر التالية تفشل في التصريف. اشتقاق
	// شرائح من مصفوفات غير قابلة للعنونة غير مسموح به.
	/*
	_ = [...]int{6, 7, 8, 9}[1:3] // خطأ
	var ma = map[string][4]int{"abc": {0, 1, 2, 3}}
	_ = ma["abc"][1:3]  // خطأ
	*/
}

وضع مدخلات يكون فيها NaN مفتاحاً في خريطة يشبه وضع المدخلات في ثقب أسود.

السبب هو NaN != NaN، وهو تفصيل آخر سيُوصَف أدناه. قبل Go 1.12، لم يكن بالإمكان العثور على العناصر التي يكون فيها NaN مفتاحاً إلا في حلقة for-range؛ ومنذ Go 1.12، يمكن أيضاً طبع العناصر التي يكون فيها NaN مفتاحاً بدوال شبيهة بـ fmt.Print.


package main

import "fmt"
import "math"

func main() {
	var a = math.NaN()
	fmt.Println(a) // NaN

	var m = map[float64]int{}
	m[a] = 123
	v, present := m[a]
	fmt.Println(v, present) // 0 false
	m[a] = 789
	v, present = m[a]
	fmt.Println(v, present) // 0 false

	fmt.Println(m) // map[NaN:789 NaN:123]
	delete(m, a)   // بلا أثر
	fmt.Println(m) // map[NaN:789 NaN:123]

	for k, v := range m {
		fmt.Println(k, v)
	}
	// الحلقة أعلاه تُخرج:
	// NaN 123
	// NaN 789
}

يرجى ملاحظة أنه قبل Go 1.12، كان نداءا fmt.Println(m) يطبعان كلاهما map[NaN:<nil> NaN:<nil>].

قد تكون سعة شريحة النتيجة لتحويل من نص إلى شريحة byte/rune أكبر من طول شريحة النتيجة.

ينبغي ألا نفترض أن طول شريحة النتيجة وسعتها متساويان دائماً.

في المثال التالي، إذا حُذف سطر fmt.Println الأخير، فإن مخرجَي السطرين السابقين له يطبعان القيمة نفسها 5؛ وإلا، فسيطبع أحدهما 5 والآخر 8 (بالنسبة لمصرِّف Go القياسي 1.25.n).


package main

import "fmt"

func main() {
	s := "abcde"
	x := []byte(s)              // len(s) == 1
	fmt.Println(cap([]byte(s))) // 32
	fmt.Println(cap(x))         // 8
	fmt.Println(x)
}

ستُكتب بعض الشيفرة المعيبة إذا افترضنا أن طول شريحة النتيجة وسعتها متساويان دائماً.

بالنسبة للشريحة s، الحلقة for i = range s {...} ليست مكافئة للحلقة for i = 0; i < len(s); i++ {...}.

قد تكون القيمتان النهائيتان لمتغير التكرار i مختلفتين بين الحلقتين.


package main

import "fmt"

var i int

func fa(s []int, n int) int {
	i = n
	for i = 0; i < len(s); i++ {}
	return i
}

func fb(s []int, n int) int {
	i = n
	for i = range s {}
	return i
}

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(fa(s, -1), fb(s, -1)) // 6 5
	s = nil
	fmt.Println(fa(s, -1), fb(s, -1)) // 0 -1
}

ترتيب التكرار على الخرائط غير مضمون أن يبقى نفسه من تكرار إلى الذي يليه.

مثلاً، البرنامج التالي لن يعمل إلى ما لا نهاية:


package main

import "fmt"

func f(m map[byte]byte) string {
	bs := make([]byte, 0, 2*len(m))
	for k, v := range m {
		bs = append(bs, k, v)
	}
	return string(bs)
}

func main() {
	m := map[byte]byte{'a':'A', 'b':'B', 'c':'C'}
	s0 := f(m)
	for i := 1; ; i++{
		if s := f(m); s != s0 {
			fmt.Println(s0)
			fmt.Println(s)
			fmt.Println(i)
			return
		}
	}
}

يرجى ملاحظة أن المدخلات في نتيجة ترميز JSON للخرائط تُرتَّب حسب مفاتيحها. ومنذ Go 1.12، فإن طبع الخرائط (بدوال الطباعة في الحزمة القياسية fmt) يُنتج أيضاً مدخلات مرتَّبة.

إذا أُنشئ مدخل خريطة أثناء تكرار على الخريطة، فقد يُكرَّر على ذلك المدخل أثناء التكرار أو قد يُتخطّى.

برهان:


package main

import "fmt"

func main() {
	m := map[int]int{0: 0, 1: 100, 2: 200}
	r, n, i:= len(m), len(m), 0
	for range m {
		m[n] = n*100
		n++
		i++
	}
	fmt.Printf("%d new entries, iterate %d and skip %d\n",
		i, i - r, n - i,
	)
}

شكراً لـ Valentin Deleplace على اقتراحَي التفصيلين أعلاه.

لا يمكن لنداء دالة متعدد النتائج أن يختلط بتعبيرات أخرى عندما يُستخدم النداء كمصادر في إسناد أو كمعطيات لنداء دالة آخر.

يرجى قراءة استخدام نداءات الدوال كتعبيرات للتفاصيل.

تُقيَّم بعض نداءات الدوال في وقت التصريف.

يرجى قراءة بعض نداءات الدوال تُقيَّم في وقت التصريف للتفاصيل.

تقابل كل طريقة دالة ضمنية.

يرجى قراءة تقابل كل طريقة دالة ضمنية للتفاصيل.

مقارنة قيمتين من نوع واجهة لهما النوع الديناميكي نفسه غير القابل للمقارنة تُنتج panic.

مثلاً:


package main

func main() {
	var x interface{} = []int{}
	_ = x == x // panic
}

يمكن استخدام تأكيدات النوع لتحويل قيمة من نوع واجهة إلى نوع واجهة آخر، حتى لو لم يكن نوع الواجهة الأول مُنفِّذاً لنوع الواجهة الثاني.

مثلاً:


package main

type Foo interface {
	foo()
}

type T int
func (T) foo() {}

func main() {
	var x interface{} = T(123)
	// السطران التاليان يفشلان في التصريف للسبب
	// نفسه: النوع interface{} لا يُنفِّذ Foo.
	/*
	var _ Foo = x   // خطأ
	var _ = Foo(x)  // خطأ
	*/
	// لكن السطر التالي يُصرَّف ويعمل بنجاح.
	var _ = x.(Foo) // صحيح
}

وجود النتيجة الثانية الاختيارية لتأكيد نوع فاشل من عدمه سيؤثر على سلوك تأكيد النوع.

إذا كانت النتيجة الثانية الاختيارية موجودة في تأكيد نوع فاشل، فلن يُنتج تأكيد النوع panic. وإلا، فسيحدث panic. مثلاً:


package main

func main() {
	var x interface{} = true

	// يفشل التأكيد، لكنه لا يُسبّب panic.
	_, _ = x.(int)

	// يفشل التأكيد، ما يُسبّب panic.
	_ = x.(int)
}

حول تأكيدات التحويل إلى واجهة المستحيلة التي يمكن كشفها في وقت التصريف.

في وقت التصريف، يمكن استنتاج أن بعض تأكيدات التحويل إلى واجهة مستحيلة النجاح. مثلاً، التأكيد الظاهر في الشيفرة التالية:


package main

type Ia interface {
	m()
}

type Ib interface {
	m() int
}

type T struct{}

func (T) m() {}

func main() {
	var x Ia = T{}
	_ = x.(Ib) // panic: main.T ليس main.Ib
}

لن تجعل مثل هذه التأكيدات عملية تصريف الشيفرة تفشل (لكن البرنامج سيدخل في panic في وقت التشغيل). منذ سلسلة أدوات Go 1.15، يُنبّه الأمر go vet على مثل هذه التأكيدات.

قيمتا error المُعادتان من نداءَي errors.New بالمعطى نفسه غير متساويتين.

السبب هو أن الدالة errors.New ستنسخ معطى النص المُدخل وتستخدم مؤشراً إلى النص المنسوخ كقيمة ديناميكية لقيمة error المُعادة. ونداءان مختلفان سيُنتجان مؤشرين مختلفين.


package main

import "fmt"
import "errors"

func main() {
	notfound := "not found"
	a, b := errors.New(notfound), errors.New(notfound)
	fmt.Println(a == b) // false
}

القنوات المستقبِلة فقط لا يمكن إغلاقها.

مثلاً، تفشل الشيفرة التالية في التصريف.


package main

func main() {
}

func foo(c <-chan int) {
	close(c) // خطأ: لا يمكن إغلاق قناة مستقبِلة فقط
}

إرسال قيمة إلى قناة مغلقة يُنظر إليه كعملية غير حاجبة، وهذه العملية تُسبّب panic.

مثلاً، في البرنامج التالي، عندما يُختار فرع case الثاني، سيُنتج panic في وقت التشغيل.


package main

func main() {
	var c = make(chan bool)
	close(c)
	select {
	case <-c:
	case c <- true: // panic: إرسال على قناة مغلقة
	default:
	}
}

يمكن تعريف الأنواع داخل أجسام الدوال.

يمكن تعريف الأنواع في أجسام الدوال. مثلاً،


package main

func main() {
	type T struct{}
	type S = []int
}

بالنسبة للمصرِّف القياسي، قد تُعامَل الحقول صفرية الحجم في بنية كقيمة بحجم بايت واحد.

يرجى قراءة بند الأسئلة الشائعة هذا للتفاصيل.

NaN != NaN، و Inf == Inf.

يتبع هذا معيار IEEE-754 ويتوافق مع معظم لغات البرمجة الأخرى:


package main

import "fmt"
import "math"

func main() {
	var a = math.Sqrt(-1.0)
	fmt.Println(a)      // NaN
	fmt.Println(a == a) // false

	var x = 0.0
	var y = 1.0 / x
	var z = 2.0 * y
	fmt.Println(y, z, y == z) // +Inf +Inf true
}

أسماء الطرق وحقول البنى غير المُصدَّرة من حزم مختلفة تُنظر إليها كأسماء مختلفة.

مثلاً، إذا عُرِّفت الأنواع التالية في الحزمة foo:


package foo

type I = interface {
	about() string
}

type S struct {
	a string
}

func (s S) about() string {
	return s.a
}

وعُرِّفت الأنواع التالية في الحزمة bar:


package bar

type I = interface {
	about() string
}

type S struct {
	a string
}

func (s S) about() string {
	return s.a
}

فإن:

  • قيم النوعين S من الحزمتين لا يمكن تحويل إحداهما إلى الأخرى.
  • نوعا الواجهة S من الحزمتين يدلان على مجموعتَي طرق مختلفتين.
  • النوع foo.S لا يُنفِّذ نوع الواجهة bar.I.
  • النوع bar.S لا يُنفِّذ نوع الواجهة foo.I.

package main

import "包2/foo"
import "包2/bar"

func main() {
	var x foo.S
	var y bar.S
	var _ foo.I = x
	var _ bar.I = y

	// الأسطر التالية تفشل في التصريف.
	x = foo.S(y)
	y = bar.S(x)
	var _ foo.I = y
	var _ bar.I = x
}

في مقارنات قيم البنى، ستُتجاهل الحقول الفارغة.

الحقول الفارغة هي الحقول التي يكون اسمها المعرّف الفارغ _. سيطبع البرنامج التالي true.


package main

import "fmt"

type T struct {
	_ int
	_ bool
}

func main() {
	var t1 = T{123, true}
	var t2 = T{789, false}
	fmt.Println(t1 == t2) // true
}

تلزم الأقواس في عدة سيناريوهات نادرة ليجري تصريف الشيفرة بنجاح.

مثلاً:


package main

type T struct{x, y int}

func main() {
	// كل سطر من الأسطر الثلاثة التالية يجعل الشيفرة
	// تفشل في التصريف. بعض الأقواس "{}" تُربك المصرِّفات.
	/*
	if T{} == T{123, 789} {}
	if T{} == (T{123, 789}) {}
	if (T{}) == T{123, 789} {}
	var _ = func()(nil) // يُنظر إلى nil كنوع
	*/

	// يجب أن نضيف أقواساً مثل السطرين التاليين
	// ليجري تصريف الشيفرة بنجاح.
	if (T{} == T{123, 789}) {}
	if (T{}) == (T{123, 789}) {}
	var _ = (func())(nil) // يُنظر إلى nil كقيمة
}

فيضان المكدس ليس panic.

بالنسبة لمصرِّفات Go السائدة حالياً، تعدّ فياضانات المكدس أخطاء قاتلة. وبمجرد حدوث فيضان مكدس، سينهار البرنامج بأكمله دون طرق للاستعادة.


package main

func f() {
	f()
}

func main() {
	defer func() {
		recover() // لا جدوى منه في منع انهيار البرنامج
	}()
	f()
}

نتيجة التشغيل:


runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
...

لمزيد من حالات الانهيار، يرجى قراءة مقال الويكي هذا.

بعض ترتيبات تقييم التعبيرات في Go تعتمد على تنفيذ المصرِّف.

يرجى قراءة ترتيبات تقييم التعبيرات في Go للتفاصيل.

قد تختلف نتائج reflect.DeepEqual(x, y) وx == y.

سيعيد نداء الدالة reflect.DeepEqual(x, y) دائماً false إذا كان نوعا معطياه مختلفين، بينما قد يعيد x == y القيمة true حتى لو كان نوعا المعامَلين بهما مختلفين.

الفرق الثاني هو أن نداء DeepEqual بمعطيين من قيمتي مؤشر من النوع نفسه يعيد ما إذا كانت القيمتان المشار إليهما بواسطة المؤشرين متساويتين بعمق أم لا. لذا قد يعيد النداء true حتى لو كان المؤشران غير متساويين.

الفرق الثالث هو أن نتيجة نداء DeepEqual قد تعيد true إذا كان كلا معطياه في سلسلتَي إشارة دائريتين (لتجنب التكرار اللانهائي في النداء).

الفرق الرابع هو أنه لا يُتوقع عموماً أن يُنتج نداء الدالة reflect.DeepEqual(x, y) panic، بينما سيُنتج x == y panic إذا كان المعامَلان بهما قيمتي واجهة ونوعاهما الديناميكيان متطابقين وغير قابلين للمقارنة.

مثال يوضح هذه الفروق:


package main

import (
	"fmt"
	"reflect"
)

func main() {
	type Book struct {page int}
	x := struct {page int}{123}
	y := Book{123}
	fmt.Println(reflect.DeepEqual(x, y)) // false
	fmt.Println(x == y)                  // true

	z := Book{123}
	fmt.Println(reflect.DeepEqual(&z, &y)) // true
	fmt.Println(&z == &y)                  // false

	type Node struct{peer *Node}
	var q, r, s Node
	q.peer = &q // تكوين سلسلة إشارة دائرية
	r.peer = &s // تكوين سلسلة إشارة دائرية
	s.peer = &r
	println(reflect.DeepEqual(&q, &r)) // true
	fmt.Println(q == r)                // false

	var f1, f2 func() = nil, func(){}
	fmt.Println(reflect.DeepEqual(f1, f1)) // true
	fmt.Println(reflect.DeepEqual(f2, f2)) // false

	var a, b interface{} = []int{1, 2}, []int{1, 2}
	fmt.Println(reflect.DeepEqual(a, b)) // true
	fmt.Println(a == b)                  // panic
}

لاحظ أنه إذا كان معطيا نداء DeepEqual كليهما قيمتي دالة، فلن يعيد النداء true إلا إذا كان معطيا الدالة كلاهما nil ونوعاهما متطابقين. وهذا مشابه لمقارنة قيم حاويات تحتوي عناصرها على قيم دوال أو مقارنة قيم بنى تحتوي حقولها على قيم دوال. لكن يرجى أيضاً ملاحظة أن نتيجة مقارنة شريحتين (من النوع نفسه) هي true دائماً إذا كانت الشريحتان تشتركان تماماً في العناصر نفسها (بعبارة أخرى، لهما الطول نفسه ولكل زوج من عناصرهما المتناظرة العنوان نفسه). مثال:


package main

import (
	"fmt"
	"reflect"
)

func main() {
	a := [1]func(){func(){}}
	b := a
	fmt.Println(reflect.DeepEqual(a, a))       // false
	fmt.Println(reflect.DeepEqual(a[:], a[:])) // true
	fmt.Println(reflect.DeepEqual(a[:], b[:])) // false
	a[0], b[0] = nil, nil
	fmt.Println(reflect.DeepEqual(a[:], b[:])) // true
}

تعيد الطريقة reflect.Value.Bytes() قيمة []byte، وقد لا يكون نوع عنصرها، byte، هو نفسه نوع قيمة الشريحة Go التي يمثلها وسيط المستقبِل.

بافتراض أن النوع الكامن لنوع معرَّف MyByte هو النوع المُعرَّف مسبقاً byte، نعلم أن نظام أنواع Go يمنع التحويلات بين قيمتَي []MyByte و[]byte. لكن يبدو أن تنفيذ الطريقة Bytes للنوع reflect.Value يخالف هذا القيد جزئياً وبشكل غير مقصود، بالسماح بتحويل قيمة []MyByte إلى []byte.

مثال:


package main

import "bytes"
import "fmt"
import "reflect"

type MyByte byte

func main() {
	var mybs = []MyByte{'a', 'b', 'c'}
	var bs []byte

	// bs = []byte(mybs) // هذا السطر يفشل في التصريف

	v := reflect.ValueOf(mybs)
	bs = v.Bytes() // صحيح. مخالفة لنظام أنواع Go.
	fmt.Println(bytes.HasPrefix(bs, []byte{'a', 'b'})) // true

	bs[1], bs[2] = 'r', 't'
	fmt.Printf("%s \n", mybs) // art
}

لكن يبدو أن هذه المخالفة ليست ضارة. بل على العكس، تحقق بعض الفوائد. فمثلاً، بفضل هذه المخالفة، يمكننا استخدام الدوال في الحزمة القياسية bytes مع قيم []MyByte.

لاحظ أن الطريقة reflect.Value.Bytes() قد تُحذف لاحقاً.

ينبغي أن نستخدم os.IsNotExist(err) بدلاً من err == os.ErrNotExist للتحقق من وجود ملف أم لا.

قد يؤدي استخدام err == os.ErrNotExist إلى تفويت أخطاء.


package main

import (
	"fmt"
	"os"
)

func main() {
	_, err := os.Stat("a-nonexistent-file.abcxyz")
	fmt.Println(os.IsNotExist(err))    // true
	fmt.Println(err == os.ErrNotExist) // false
}

بالنسبة للمشاريع التي تدعم Go 1.13+ فقط، يُوصى أكثر باستخدام errors.Is(err, os.ErrNotExist) للتحقق من وجود ملف أم لا.


package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	_, err := os.Stat("a-nonexistent-file.abcxyz")
	fmt.Println(errors.Is(err, os.ErrNotExist)) // true
}

تعامل الحزمة القياسية flag الأعلام المنطقية للأوامر بشكل مختلف عن أعلام الأعداد الصحيحة والنصوص.

توجد ثلاث صيغ لتمرير خيارات الأعلام.

  1. -flag، للأعلام المنطقية فقط.
  2. -flag=x، لأي علم.
  3. -flag x، للأعلام غير المنطقية فقط.

ويرجى ملاحظة أن العلم المنطقي بالصيغة الأولى يُنظر إليه كالعلم الأخير، وتُعامل جميع العناصر التالية له كمعطيات.


package main

import "fmt"
import "flag"

var b = flag.Bool("b", true, "a boolean flag")
var i = flag.Int("i", 123, "an integer flag")
var s = flag.String("s", "hi", "a string flag")

func main() {
	flag.Parse()
	fmt.Print("b=", *b, ", i=", *i, ", s=", *s, "\n")
	fmt.Println("arguments:", flag.Args())
}

إذا شغّلنا هذا البرنامج بالأعلام والمعطيات الموضحة أدناه


./exampleProgram -b false -i 789 -s bye arg0 arg1

فستكون المخرجات


b=true, i=123, s=hi
arguments: [false -i 789 -s bye arg0 arg1]

من الواضح أن هذه المخرجات ليست ما نتوقعه.

ينبغي أن نمرر الأعلام والمعطيات مثل


./exampleProgram -b=false -i 789 -s bye arg0 arg1

أو


./exampleProgram -i 789 -s bye -b arg0 arg1

للحصول على المخرجات التي نتوقعها:


b=true, i=789, s=bye
arguments: [arg0 arg1]

تدعم دوال [Sp|Fp|P]rintf المعطيات الموضعية.

سيطبع البرنامج التالي coco.


package main

import "fmt"

func main() {
	// السطر التالي يطبع: coco
	fmt.Printf("%[2]v%[1]v%[2]v%[1]v", "o", "c")
}