Date:

Generate single title from this title Don’t Cry about Pointers anymore – Deep Dive with C, Go and Rust in 100 -150 characters. And it must return only title i dont want any extra information or introductory text with title e.g: ” Here is a single title:”

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;
}
Enter fullscreen mode

Exit fullscreen mode

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
Enter fullscreen mode

Exit fullscreen mode

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
Enter fullscreen mode

Exit fullscreen mode

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()
Enter fullscreen mode

Exit fullscreen mode

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);
Enter fullscreen mode

Exit fullscreen mode

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

Enter fullscreen mode

Exit fullscreen mode

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
}
Enter fullscreen mode

Exit fullscreen mode

fn main() {
    let a = 10;
    let p = &a; // immutable reference
    println!("{}", *p);
}

Enter fullscreen mode

Exit fullscreen mode




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;

Enter fullscreen mode

Exit fullscreen mode

p := new(int)
*p = 42

Enter fullscreen mode

Exit fullscreen mode

let b = Box::new(42); // heap allocation

Enter fullscreen mode

Exit fullscreen mode




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
}
Enter fullscreen mode

Exit fullscreen mode



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
}
Enter fullscreen mode

Exit fullscreen mode



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.

Stack and Heap Allocation



C Example:

int *p = malloc(sizeof(int));
*p = 99;
free(p);
Enter fullscreen mode

Exit fullscreen mode



Rust Example:

let b = Box::new(5); // heap allocation
println!("{}", b);  // Box implements Deref, so it acts like a pointer
Enter fullscreen mode

Exit fullscreen mode



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
}
Enter fullscreen mode

Exit fullscreen mode



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 v/s Pass by reference

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
}
Enter fullscreen mode

Exit fullscreen mode

Pass-by-Reference using Pointer

void modify(int *a) {
    *a = 100;
}

int main() {
    int x = 10;
    modify(&x);
    printf("%d\n", x); // prints 100
}
Enter fullscreen mode

Exit fullscreen mode

Pass-by-Value (default in Go)

func modify(a int) {
    a = 100
}

func main() {
    x := 10
    modify(x)
    fmt.Println(x) // prints 10
}
Enter fullscreen mode

Exit fullscreen mode

Pass-by-Reference using Pointers

func modify(a *int) {
    *a = 100
}

func main() {
    x := 10
    modify(&x)
    fmt.Println(x) // prints 100
}
Enter fullscreen mode

Exit fullscreen mode

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
}
Enter fullscreen mode

Exit fullscreen mode

Pass-by-Reference (Borrowing)

fn modify(a: &mut i32) {
    *a = 100;
}

fn main() {
    let mut x = 10;
    modify(&mut x);
    println!("{}", x); // prints 100
}
Enter fullscreen mode

Exit fullscreen mode

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() or make() (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])
Enter fullscreen mode

Exit fullscreen mode

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!
}

Enter fullscreen mode

Exit fullscreen mode




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 of null

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 or Box::leak

Example:

let x = Some(10);
let y: Option<&i32> = None; // instead of a null pointer
Enter fullscreen mode

Exit fullscreen mode

let a = [1, 2, 3];
let p = a.as_ptr();
unsafe {
    println!("{}", *p.add(1)); // Pointer arithmetic in unsafe block
}
Enter fullscreen mode

Exit fullscreen mode




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 backgroundyou 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
}
Enter fullscreen mode

Exit fullscreen mode



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);
}
Enter fullscreen mode

Exit fullscreen mode



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:

  1. Direct memory referencing with & and *
  2. Lightweight object sharing via pointer receivers
  3. Safe heap allocation using new()
  4. 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
}
Enter fullscreen mode

Exit fullscreen mode

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
}
Enter fullscreen mode

Exit fullscreen mode




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.

Bye Bye

.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:”

Latest stories

Read More

Generate single title from this title AWS Amplifyの古いハンズオンを実施してハマった話 in 100 -150 characters. And it must return only title i dont want any extra information...

Write an article about JP Contents Hubには多くのサービスに関するハンズオンが掲載されており、少しでも触っていないサービスを触ろうとハンズオンにチャレンジする際に有意義なコンテンツとなっているが、CI/CD for AWS...

LEAVE A REPLY

Please enter your comment!
Please enter your name here