Pointers ও Memory
Pointer আপনাকে ডেটা কপি না করেই শেয়ার করতে দেয় — একবার বুঝে নিলে & আর * নিয়ে আর কখনো ধন্দে পড়বেন না।
গল্পে বুঝি
ইবনে সিনা শহরের বাইরে থাকে, তার একটা বাড়ি আছে গ্রামে। বাড়ির রঙ চটে গেছে, তাই সে আল-খোয়ারিজমিকে বলল, “ভাই, আমার বাড়িটা একটু রং করিয়ে দে তো।” এখন আল-খোয়ারিজমিকে তো কাজটা করতে হলে জানতে হবে বাড়িটা ঠিক কোথায়। ইবনে সিনা একটা কাগজে বাড়ির পুরো ঠিকানা লিখে আল-খোয়ারিজমির হাতে দিল — গ্রামের নাম, রাস্তার নাম, বাড়ির নম্বর। আল-খোয়ারিজমি সেই ঠিকানা ধরে গ্রামে গেল, আসল বাড়িটা খুঁজে বের করল, আর দেয়ালে নতুন রং লাগিয়ে দিল। ইবনে সিনা পরে গিয়ে দেখল — বাহ, তার আসল বাড়িটাই সত্যিই ঝকঝকে হয়ে গেছে।
এবার উল্টোটা ভাবুন। ইবনে সিনা যদি আল-খোয়ারিজমিকে ঠিকানা না দিয়ে বাড়ির একটা ছবির ফটোকপি হাতে দিত আর বলত “এটা রং কর”? আল-খোয়ারিজমি কাগজের ওই ছবিতে যত সুন্দর করেই রং বুলাক না কেন, গ্রামের আসল বাড়ির একটা ইটও বদলাবে না। কারণ সে তো শুধু একটা কপি নিয়ে কাজ করছে, আসল জিনিসটার নাগাল তার হাতে নেই।
এই গল্পটাই আসলে pointer। বাড়ির ঠিকানা লেখা কাগজটাই হলো pointer — সে নিজে বাড়ি না, বাড়িটা কোথায় আছে সেই address ধরে রাখে। ঠিকানা (pointer) হাতে পেলে আল-খোয়ারিজমি আসল বাড়িটাই (original value) বদলাতে পারে। আর ফটোকপি দেওয়া মানে একটা value copy পাস করা — Go-তে ডিফল্টে ফাংশনে সবকিছু এভাবেই copy হয়ে যায়, তাই কপিতে করা পরিবর্তন আসলটায় লাগে না। বাস্তবে কোনো ফাংশনকে যখন আপনি আসল ডেটা বদলাতে দিতে চান — যেমন একটা struct-এর field আপডেট করা — তখন value পাঠানোর বদলে তার address, মানে pointer, পাঠান।
Pointer কী?
একটা pointer হলো এমন একটা variable যা আরেকটা variable-এর memory address ধরে রাখে। মানটা নিজে ধরে রাখার বদলে, এটা মানটা যেখানে আছে সেখানকার দিকনির্দেশনা ধরে রাখে।
name := "Fatima" // A string variable
ptr := &name // A pointer to that string (&name = "address of name")
fmt.Println(name) // "Fatima" — the value
fmt.Println(ptr) // 0xc0000b4000 — the memory address
fmt.Println(*ptr) // "Fatima" — dereferencing: follow the address to get the value বাস্তব জীবনের উপমা
একটা pointer অনেকটা কাগজে লেখা একটা বাড়ির ঠিকানার মতো। কাগজটায় বাড়ি নেই — এতে বাড়িটা যেখানে আছে সেই ঠিকানা আছে। &name আপনাকে ঠিকানা দেয়। *ptr অনেকটা সেই ঠিকানায় গাড়ি চালিয়ে গিয়ে বাড়িটা দেখার মতো। একাধিক মানুষের কাছে একই ঠিকানা লেখা থাকতে পারে, আর তারা সবাই একই বাড়িটাই দেখবে।
Pointer কেন আছে
Go সবকিছু by value পাস করে — function argument সবসময় copy। Pointer ছাড়া আপনি মূল ডেটা পরিবর্তন করতে পারবেন না:
// WITHOUT pointers — the original doesn't change
func tryToModify(name string) {
name = "Omar" // Modifies the COPY, not the original
}
original := "Fatima"
tryToModify(original)
fmt.Println(original) // Still "Fatima"
// WITH pointers — you modify the original
func actuallyModify(name *string) {
*name = "Omar" // Follow the pointer, modify what's there
}
original := "Fatima"
actuallyModify(&original) // Pass the address
fmt.Println(original) // "Omar" — it changed! দুটি Operator
| Operator | নাম | কী করে | উদাহরণ |
|---|---|---|---|
& | Address-of | একটা variable-এর memory address নেয় | ptr := &name |
* | Dereference | value পেতে/সেট করতে pointer ধরে এগোয় | value := *ptr |
x := 42
p := &x // p is *int (pointer to int)
fmt.Println(p) // 0xc0000b4008 (memory address)
fmt.Println(*p) // 42 (value at that address)
*p = 100 // Change the value through the pointer
fmt.Println(x) // 100 — x changed because p points to x Pointer Types
*T type-এর মানে “T type-এর একটা value-র দিকে pointer”:
var intPtr *int // Pointer to int (zero value is nil)
var strPtr *string // Pointer to string
var userPtr *User // Pointer to User struct
// Creating pointers
num := 42
intPtr = &num // Point to existing variable
// Or use new() — allocates and returns pointer
intPtr = new(int) // Points to a new int (value = 0)
*intPtr = 42 // Set the value Nil Pointers
একটা pointer-এর zero value হলো nil। একটা nil pointer dereference করলে panic (crash) হয়:
var ptr *int // nil
fmt.Println(ptr) // <nil>
// fmt.Println(*ptr) // PANIC: runtime error: invalid memory address
// Always check for nil before dereferencing
if ptr != nil {
fmt.Println(*ptr)
} Nil pointer dereference হলো Go-র সবচেয়ে সাধারণ runtime panic। *ptr ব্যবহারের আগে একটা pointer nil হতে পারে কিনা সবসময় চেক করুন। struct field, function return, আর interface value-র ক্ষেত্রে এটা বিশেষভাবে গুরুত্বপূর্ণ।
Pointer এবং Structs
এখানেই বাস্তব Go কোডে pointer অপরিহার্য হয়ে ওঠে:
type User struct {
Name string
Email string
Age int
}
// Without pointer — works on a COPY (56+ bytes copied)
func birthday(u User) {
u.Age++ // Only modifies the copy
}
// With pointer — works on the ORIGINAL (8 bytes copied — just the address)
func birthday(u *User) {
u.Age++ // Modifies the actual user
// Note: Go automatically dereferences — no need to write (*u).Age++
}
user := &User{Name: "Fatima", Age: 29}
birthday(user)
fmt.Println(user.Age) // 30 বাস্তব জীবনের উপমা
একটা struct-কে by value পাস করা অনেকটা একটা নথির ফটোকপি করে কপিটা হাতে দেওয়ার মতো — কপিতে করা এডিট মূলটায় প্রভাব ফেলে না। একটা pointer পাস করা অনেকটা একটা Google Doc-এর লিংক শেয়ার করার মতো — লিংক থাকা সবাই একই নথি এডিট করে।
Automatic Dereferencing
Go struct pointer access সহজ করে দেয় — আপনার (*ptr).Field লাগে না:
user := &User{Name: "Fatima"}
// These are equivalent:
fmt.Println((*user).Name) // Explicit dereference
fmt.Println(user.Name) // Go does it automatically Stack বনাম Heap
Go automatically memory ম্যানেজ করে, কিন্তু ডেটা কোথায় থাকে সেটা বোঝা দ্রুততর কোড লিখতে সাহায্য করে:
// Stack allocation (fast — automatic cleanup)
func stackExample() int {
x := 42 // Lives on the stack
return x // Copied out, stack frame released
}
// Heap allocation (slower — needs garbage collection)
func heapExample() *int {
x := 42 // x ESCAPES to the heap because we return a pointer to it
return &x // Go detects this and allocates x on the heap
} বাস্তব জীবনের উপমা
Stack = অফিসে আপনার ডেস্ক। জিনিস রাখা আর তোলা দ্রুত, কিন্তু আপনি দিন শেষে চলে গেলে (function return) আপনার ডেস্ক পরিষ্কার হয়ে যায়। Heap = কোম্পানির স্টোরেজ রুম। কারো কাছে চাবি (pointer) থাকা পর্যন্ত জিনিস সেখানে থাকে। ঝাড়ুদার (garbage collector) মাঝেমধ্যে দেখে কোন জিনিসের দিকে আর কোনো চাবি তাক করা নেই আর সেগুলো পরিষ্কার করে।
Escape Analysis
Go-র compiler ঠিক করে stack-এ নাকি heap-এ allocate করবে। আপনি তার সিদ্ধান্তগুলো দেখতে পারেন:
go build -gcflags="-m" ./...
# ./main.go:10:2: x escapes to heap
# ./main.go:15:2: y does not escape মূলনীতি:
- আপনি যদি একটা local variable-এর দিকে pointer return করেন, তা heap-এ escape করে
- আপনি যদি একটা দীর্ঘস্থায়ী struct-এ একটা pointer store করেন, তা escape করে
- একটা variable যদি stack-এর জন্য বড্ড বড় হয়, তা heap-এ যায়
- Stack allocation প্রায় ফ্রি; heap allocation-এ GC জড়িত
সাধারণ Patterns
Optional Values
Go-তে Optional বা Maybe নেই। Pointer এই উদ্দেশ্য পূরণ করে:
type SearchParams struct {
Query string
MinPrice *float64 // nil = not specified
MaxPrice *float64 // nil = not specified
Page *int // nil = use default
}
func search(params SearchParams) []Product {
query := db.Where("name LIKE ?", params.Query)
if params.MinPrice != nil {
query = query.Where("price >= ?", *params.MinPrice)
}
if params.MaxPrice != nil {
query = query.Where("price <= ?", *params.MaxPrice)
}
page := 1
if params.Page != nil {
page = *params.Page
}
// ...
}
// Helper for creating pointers to literals
func Ptr[T any](v T) *T {
return &v
}
// Usage
search(SearchParams{
Query: "laptop",
MinPrice: Ptr(500.0),
// MaxPrice is nil — no upper limit
}) বড় Copy এড়ানো
type Report struct {
Title string
Data [10000]float64 // 80KB!
Metadata map[string]string
}
// BAD: copies 80KB+ every call
func processReport(r Report) { ... }
// GOOD: passes 8 bytes (the pointer)
func processReport(r *Report) { ... } কখন Pointer বনাম Value ব্যবহার করবেন
Pointer *T ব্যবহার করুন | Value T ব্যবহার করুন |
|---|---|
| মূলটা পরিবর্তন করা দরকার | Read-only access |
| Struct বড় (>64 bytes) | ছোট struct (Point, Color, Time) |
| “optional” প্রকাশ করা (nil = অনুপস্থিত) | Value সবসময় দরকার |
| goroutine-এর মধ্যে ডেটা শেয়ার করা | স্বাধীন copy ঠিক আছে |
| pointer receiver দিয়ে একটা interface পূরণ করা | Immutable data type |
সন্দেহ হলে, struct-এর জন্য pointer ব্যবহার করুন। ছোট struct-এর জন্য performance-এর পার্থক্য নগণ্য, কিন্তু pointer semantics (পরিবর্তনযোগ্যতা, nil হওয়ার সম্ভাবনা) সাধারণত application কোডে আপনি যা চান তা-ই। ছোট, immutable type যেমন time.Time, netip.Addr, বা আপনার নিজের Money type-এর জন্য value ব্যবহার করুন।
মূল যেসব শিখলেন
&address নেয়,*address ধরে এগোয় — pointer-এর ব্যাপার এটুকুই- Go pass-by-value — pointer ছাড়া function copy নিয়ে কাজ করে
- Nil pointer dereference হলো #1 panic —
*ptrব্যবহারের আগে সবসময় চেক করুন - Go struct pointer auto-dereference করে —
userUserহোক বা*User,user.Nameকাজ করে - Stack দ্রুত, heap-এ GC লাগে — local-এর দিকে pointer return করলে সেগুলো heap-এ চলে যায়
- Optionality-র জন্য pointer ব্যবহার করুন —
*float64যেখানে nil মানে “specified নয়” - বড় struct-এর জন্য pointer ব্যবহার করুন — প্রতি function কল-এ কিলোবাইট ডেটা copy করা এড়ায়