Write an article about
Why TF does this blog even exist?
Let’s be real — most of us treat pointers like radioactive waste. Let’s ignore those dreadful stories of childhood when pointers were introduced to you like a BIG demon. But deep down, the truth is:
Memory is power. And pointers? Pointers are your keys to the throne.
If you’ve ever:
- Debugged a nasty segfault while chugging a huge load of coffee,
- Seen your app lag thanks to the garbage collector,
- Or tried passing huge structs around to make your code modular …
Then you’ve already felt the need for pointers.
This isn’t just a blog. This is a manifesto. We’re gonna break the myths, smash the fear, and teach you how to own memory like an absolute menace — across C (just for understanding how pointers work), Go, and Rust.
By the end of this read, you’ll understand:
- What pointers really are
- Why they matter in modern backend systems
- How Go gives you a chill but powerful pointer experience
- How Rust forces you into greatness with ownership and borrowing
Let’s cut the fluff and jump right in — welcome to the deep dive.
Basics: Pointers and How They Work (I’m Hoping Y’all Know C)
A pointer just refers to another value. That’s it. It is itself a variable that stores the address of another variable(this variable can be a pointer too).
Let’s see a quick code snippet in C to recap pointer basics.
#include
int main() {
int x = 42;
int *p = &x; // p points to the address of x
printf("Value of x: %d\n", x);
printf("Pointer p: %p\n", p); // prints address of x
printf("Value at *p: %d\n", *p); // dereferencing p, gives 42
*p = 99; // update x through the pointer
printf("Updated x: %d\n", x); // now x is 99
return 0;
}
Key Concepts :
- *p → Dereference the pointer (get the value it points to)
- &x → Address-of operator (get the memory address of x)
- int *p → Declare a pointer to an integer
This is where C shows you its true colors: dope performance, minimal overhead — but a single wrong move and it’s undefined behavior land. Manual memory management is both a blessing and a curse.
Let’s quickly take a look over other pointer shenanigans in C :
1. Pointers and Arrays:
int arr[3] = {10, 20, 30};
int *ptr = arr; // same as &arr[0]
printf("%d\n", *(ptr + 1)); // prints 20
Arrays decay into pointers — the name of an array is basically a pointer to its first element.
2. Pointer to Pointer:
int x = 5;
int *p = &x;
int **pp = &p;
printf("%d\n", **pp); // double dereference
A pointer to a pointer holds the address of another pointer. Useful in dynamic memory, linked lists, and certain APIs.
3. Function Pointers:
void greet() { printf("Hello!\n"); }
void (*funcPtr)() = greet;
funcPtr(); // calls greet()
Functions live in memory too, and you can point to them! That’s how callbacks and plugin systems are built.
4. Void Pointers:
void *vp;
int a = 7;
vp = &a;
printf("%d\n", *(int *)vp);
Generic pointers that can point to any type. But you need to cast them back before dereferencing.
Wait! There is also pointer arithmetic in C.
In C, pointers aren’t just addresses — they’re math-capable beasts. You can add or subtract integers to pointers to move across array elements (ptr + 1 points to the next element).
But beware: mess up the math, and you’re one *(ptr + 9999) away from summoning a segfault demon.(I swear ChatGPT wrote this!)
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
printf("%d\n", *(ptr + 2)); // prints 3
I just skimmed through the surface of pointers in C. You definitely need to go into a lot more detail. If you want to work with pointers in C, burn this video into your head.
In C, you need to manage memory manually using malloc or calloc and use the pointers very carefully and cautiously. While you have all the control, things can go south easily, even if you miss a little something.
Before diving into how we handle pointer in Go or Rust, let us look at some common concepts in pointers.
Pointer 101
Pointer Declaration
We already saw how to do this in C, and it is pretty easy in Go and Rust too. These code snippets will help you grasp it.
func main() {
a := 10
var p *int = &a
fmt.Println(*p) // dereferencing
}
fn main() {
let a = 10;
let p = &a; // immutable reference
println!("{}", *p);
}
Dynamic Memory Allocation
Even here, the code snippets will help you. The pointers are allocated memory in heap.
int *p = (int*) malloc(sizeof(int));
*p = 42;
p := new(int)
*p = 42
let b = Box::new(42); // heap allocation
Stack vs Heap Allocation in Pointers
When working with pointers in low-level languages like C, Go, and Rust, understanding how memory is allocated — either on the stack or the heap — is non-negotiable if you don’t want your programs to randomly explode (aka segfault).
Stack Allocation
- Fast and automatically managed.
- Memory is allocated when a function is called and freed when it returns.
- Ideal for short-lived variables and function-local data.
C Example:
int main() {
int x = 42; // x is on the stack
int *p = &x; // p points to stack memory
}
Warning:
Returning a pointer to a stack variable is dangerous. Once the function exits, that memory is gone.
int* bad() {
int local = 10;
return &local; // 🚨 Undefined behavior
}
Heap Allocation
- Slower but gives manual control (malloc/free in C,
Box::new
in Rust). - Data lives until you explicitly free it (or a garbage collector does).
- Used for long-lived or large data structures.
C Example:
int *p = malloc(sizeof(int));
*p = 99;
free(p);
Rust Example:
let b = Box::new(5); // heap allocation
println!("{}", b); // Box implements Deref, so it acts like a pointer
Go’s Sweet Spot
Go doesn’t let you explicitly allocate on heap or stack — the compiler decides based on escape analysis.
func makePointer() *int {
x := 10
return &x // may be heap-allocated if it escapes
}
TL;DR
Feature | Stack | Heap |
---|---|---|
Lifetime | Until function returns | Until explicitly freed |
Speed | Fast | Slower |
Managed by | Compiler | Developer / GC |
Use case | Small, short-lived data | Large, persistent data |
Typical Bug | Use-after-return | Memory leaks, double free |
Stack = speed.
Heap = flexibility.
Misunderstanding either = pain.
Pass By Value v/s Pass By Reference
What is Pass-by-Value?
- A copy of the variable is passed to the function.
- The original variable does not change.
What is Pass-by-Reference?
- A reference or pointer is passed.
- The function can modify the original variable.
Pass-by-Value (default in C)
void modify(int a) {
a = 100;
}
int main() {
int x = 10;
modify(x);
printf("%d\n", x); // prints 10
}
Pass-by-Reference using Pointer
void modify(int *a) {
*a = 100;
}
int main() {
int x = 10;
modify(&x);
printf("%d\n", x); // prints 100
}
Pass-by-Value (default in Go)
func modify(a int) {
a = 100
}
func main() {
x := 10
modify(x)
fmt.Println(x) // prints 10
}
Pass-by-Reference using Pointers
func modify(a *int) {
*a = 100
}
func main() {
x := 10
modify(&x)
fmt.Println(x) // prints 100
}
Even slices, maps, channels in Go are passed by value, but since the value contains an internal pointer, they often behave like references.
Rust always passes by value, but with borrowing (&
) or mutable borrowing (&mut
), you simulate reference behavior safely.
Pass-by-Value (moves or copies the value)
fn modify(mut a: i32) {
a = 100;
}
fn main() {
let x = 10;
modify(x);
println!("{}", x); // prints 10
}
Pass-by-Reference (Borrowing)
fn modify(a: &mut i32) {
*a = 100;
}
fn main() {
let mut x = 10;
modify(&mut x);
println!("{}", x); // prints 100
}
Rust ensures no data races or dangling pointers using its ownership model and borrow checker. (We will understand more of this later)
Side-by-Side Summary
Language | Default Behavior | Reference Support | Mutable Ref Support | Safety Mechanism |
---|---|---|---|---|
C | Pass-by-Value | ✅ Yes (manual * ) |
✅ Yes | ❌ Manual, unsafe |
Go | Pass-by-Value | ✅ Yes (with *T ) |
✅ Yes | ❌ Garbage-collected |
Rust | Pass-by-Value | ✅ Yes (&T ) |
✅ Yes (&mut T ) |
✅ Ownership & Borrowing |
Key Facts and Gotchas — Pointers in C, Go, and Rust
C
You can:
- Do pointer arithmetic
- Use null pointers
- Cast between pointer types
- Create pointer-to-pointer
- Manually manage heap (malloc/free)
- Point to functions
You must handle:
- No safety — undefined behavior is easy
- Manual memory management (malloc/free)
- Dangling pointers, memory leaks, buffer overflows
- Segfaults due to wild pointers
Go
You can:
- Use pointers to access and modify values
- Pass pointers to functions
- Allocate memory with
new()
ormake()
(for slices, maps, etc.)
But you cannot:
- Do pointer arithmetic
- Create pointer-to-pointer (
**int
) - Manually free memory (garbage collected)
- Have nil-check safety for all types (you can nil dereference)
- Take pointer to literal (
p := &10
is invalid)
🔹 Example:
arr := []int{1, 2, 3}
// Can't do: ptr := &arr[0] + 1 ❌
// Use indexing instead
fmt.Println(arr[1])
As said earlier, Go passes structs/slices by value, but since slices contain an internal pointer, they behave “reference-like.”
See :
func modify(s []int) {
s[0] = 999
}
func main() {
a := []int{1, 2, 3}
modify(a)
fmt.Println(a) // [999 2 3] - gotcha!
}
Rust
You can:
- Use references (
&T
,&mut T
) safely - Use
Box
,Rc
,Arc
for heap allocation and shared ownership - Use raw pointers in unsafe blocks (
*const
,*mut
) - Use
Option
instead ofnull
You cannot:
- Have null pointers (
None
is used instead) - Mutably borrow more than once simultaneously (borrow checker)
- Dereference or do pointer arithmetic outside
unsafe
- Leak memory unless you
std::mem::forget
orBox::leak
Example:
let x = Some(10);
let y: Option<&i32> = None; // instead of a null pointer
let a = [1, 2, 3];
let p = a.as_ptr();
unsafe {
println!("{}", *p.add(1)); // Pointer arithmetic in unsafe block
}
TL;DR: Cheat Sheet
Feature / Language | C | Go | Rust |
---|---|---|---|
Null Pointers | ✅ NULL
|
✅ nil
|
❌ Use Option
|
Pointer Arithmetic | ✅ Yes | ❌ No | ✅ Yes (unsafe only) |
Pointer-to-Pointer | ✅ Yes | ❌ No | ✅ Yes (&&T ) |
Automatic Memory Mgmt | ❌ No | ✅ GC | ✅ Ownership model |
Dangling Pointer Safety | ❌ No | ❌ No | ✅ Checked at compile |
Struct Ref Mutability | ✅ Manual | ✅ Pointers | ✅ Borrow system |
Ref Counted Shared Ptr | ❌ Manual | ✅ Maps etc. | ✅ Rc , Arc
|
Manual Free | ✅ Yes | ❌ No | ✅ Drop trait |
Function Pointers | ✅ Yes | ✅ Yes | ✅ fn , Fn , FnMut
|
Garbage Collection in Go v/s Ownership in Rust
Choose your weapon: Lazy janitor vs Memory tyrant.
Go’s Garbage Collection (GC) — The Chill Roommate
Go takes a comfy, high-level approach. You allocate memory, use it, and forget it — the Go runtime’s GC cleans it up like a silent ninja.
How it Works:
- Go uses a concurrent, tri-color mark-and-sweep garbage collector.
- It pauses briefly, walks through live objects, marks them, and frees unmarked (unreachable) memory.
- GC runs in the background — you don’t control it directly.
Example:
type User struct {
name string
}
func main() {
u := &User{name: "pixie"}
fmt.Println(u.name) // GC takes care of `u` when it's no longer used
}
Pros:
- Zero manual memory management.
- Great for productivity, safety, and fast dev cycles.
- No need to think about
free()
or memory leaks most of the time.
Cons:
- GC pauses = potential latency spikes.
- GC can kick in at random times = non-deterministic behavior.
- Less control over performance-sensitive workloads (e.g., real-time systems).
GC is like Uber Eats: you chill, your trash gets picked up eventually — but not always when you want it.
Rust’s Ownership Model — The Memory Warlord’s Code
Rust said:
“Why GC when you can just never make a memory mistake?”
Instead of garbage collection, Rust uses ownership + lifetimes + borrowing to statically guarantee memory safety — at compile time.
Core Concepts:
- Ownership: Every value has a single owner.
- Borrowing: You can lend references without transferring ownership.
- Lifetimes: Ensures references are valid as long as they’re used.
fn main() {
let s = String::from("hello"); // s owns the String
takes_ownership(s); // ownership moved, s is now invalid
// println!("{}", s); // ❌ Compile error
let x = 5;
makes_copy(x); // i32 implements Copy, x still valid
}
fn takes_ownership(s: String) {
println!("{}", s);
}
fn makes_copy(x: i32) {
println!("{}", x);
}
Pros:
- Zero runtime cost (no GC!)
- Compile-time guarantees = no segfaults, no leaks, no data races.
- Absolute control — systems level performance, with high-level safety.
Cons:
- Steeper learning curve (borrow checker will roast you alive at first).
- More verbose code with
&
,&mut
, lifetimes. - Need to think deeply about lifetimes and ownership flows.
Rust is like being Batman: no sidekicks, no help — just you and your gear. But once you master it? You’re unstoppable.
TL;DR — Go GC vs Rust Ownership
Feature | Go (Garbage Collected) | Rust (Ownership Model) |
---|---|---|
Memory Management | Automatic via GC | Compile-time via Ownership |
Control | Minimal | Complete |
Performance Overhead | GC pauses, unpredictable latency | Near-zero runtime cost |
Memory Safety | Mostly safe, but nil pointers exist | Guaranteed via borrow checker |
Ease of Use | Easy, chill | Steep learning curve, then power |
Real-time Systems | ❌ Not ideal | ✅ Excellent fit |
Some Wisdom Drop
- If you want developer productivity with decent performance: Go.
- If you want maximum performance with zero runtime overhead and are willing to tame the beast: Rust.
- Want to be a backend god-tier villain? Learn both. Use the right tool for the right kill.
Chaos to Calm – Pointers in Go
So now that you’ve stared into the void (pointer pun intended) with C — raw, dangerous, and obviously a double edged sword — let’s take a deep breath and walk into a language that hands you pointers… but without the existential crisis.
Go doesn’t give you pointer arithmetic. It won’t let you shoot yourself in the foot (most of the time). But it does give you:
- Direct memory referencing with & and *
- Lightweight object sharing via pointer receivers
- Safe heap allocation using new()
- And just enough low-level vibes to keep your inner villain happy
By the above examples , it must be pretty clear how pointers work in go, let’s jump to working with structs and pointers in go.
Pointers and Structs in Go
Structs in Go are value types by default. This means assigning a struct or passing it to a function copies the whole thing — which could suck for performance.
When to Use Pointers with Structs
- When your struct is large and copying is expensive
- When you want to mutate the original struct in a function
- When you want to avoid frequent allocations in tight loops
type Villain struct {
Name string
PowerLevel int
}
func boost(v *Villain) {
v.PowerLevel += 100
}
func main() {
v := Villain{"Yashaswi", 9000}
boost(&v)
fmt.Println(v.PowerLevel) // 9100
}
When NOT to Use Pointers
- If your struct is small (e.g., a few fields) and used immutably
- If you don’t need to share/mutate state across functions
- If you’re creating short-lived structs inside a loop — prefer values to help stack allocation
Rule of Thumb
- If it’s less than or equal to 3 words (e.g., 24 bytes), pass by value.
- If you need shared/mutable state, use pointers.
- For methods, use pointer receivers if the method needs to mutate or avoid copying.
type Engine struct {
HorsePower int
}
func (e Engine) PrintHP() { // value receiver
fmt.Println(e.HorsePower)
}
func (e *Engine) Boost() { // pointer receiver
e.HorsePower += 100
}
Memory Best Practices in Go
Avoid unnecessary allocations
- Use value receivers where mutation isn’t needed.
- Preallocate slices and maps where possible.
Escape analysis matters
- If a variable escapes to the heap (vs staying on the stack), it increases GC pressure.
- Use
go build -gcflags=-m
to check if variables escape.
Use sync.Pool
for reusable memory
- Great for reducing GC load in high-performance apps.
Don’t fear pointers
- They’re safe, controlled, and let you avoid copying large structs.
Mind the zero values
- In Go, everything has a zero value. Don’t waste allocations initializing something that’s already zero.
### ⚠️ What Go Doesn’t Let You Do (And That’s Okay)- No manual malloc/free.
- No pointer arithmetic.
- No direct memory manipulation (you’ll need
unsafe
for that).
But hey, that’s the point — Go protects you from footguns so you can ship faster without being reckless. You’re still working with memory, just with guardrails.
That’s a wrap
And there you have it — the journey from raw pointers to Go vs. Rust. We’ve covered how memory is managed at its most basic level, the stack vs. heap debate, and the power (and peril) of manual memory management.
In the world of Rust, we’ve seen how the ownership system isn’t just a feature — it’s a whole new way to think about memory safety. With borrowing and lifetimes, Rust enforces a discipline that forces you to think ahead, leaving no room for memory leaks or dangling pointers. It’s a challenge, but one that builds robust, zero-cost abstractions.
In Go, we saw how things are more laid-back with garbage collection and pointers that you only really need when performance or interop is key. Go’s approach has its benefits, but it doesn’t give you the same fine-grained control as Rust.
Remember: pointers are a tool. Whether you’re dealing with them in Go or Rust, you control the power — but with great power comes great responsibility. Master these concepts, and you’ll be writing software that’s not just efficient, but invincible.
Also, we’ve only scratched the surface with Rust. In the coming one, we’ll dive into the real villain territory of Rust’s ownership, smart pointers, lifetimes, threads, and channels. Are you ready to level up your backend skills?
Until then, keep your pointers sharp and your memory clean.
.Organize the content with appropriate headings and subheadings ( h2, h3, h4, h5, h6). Include conclusion section and FAQs section with Proper questions and answers at the end. do not include the title. it must return only article i dont want any extra information or introductory text with article e.g: ” Here is rewritten article:” or “Here is the rewritten content:”