If pointers are the dynamite that gets laid in a tunnel then dangling pointers are the matchstick that will inevitably, accidentally light it
That’s a pretty scary quote but it resonates throughout many large scale projects that utilise memory management (which is a lot). So what exactly is a dangling pointer and why is it so dangerous? Let’s find out! First, we’ll discuss what it is, why it’s so dangerous and then have a look at how a new programming language, Rust, fixes it.
A pointer is a fixed size integer that points to a location in memory. The size of the integer is determined by the architechture of the system you are using — For example, 32 bit vs 64 bit. At this location is some form of bytes that hopefully, but not always, contains some sane data. As an example , consider that we have some
int and we want to create a pointer to it:
Ok so now we have a pointer that points to some memory address and at that memory address is the number 5. So far, so good. However, this is not very good coding practice. Typically, we want to isolate behaviour to their respective functions; In this case, that means moving this behaviour outside of
main and creating a new function that returns a pointer to
This leads us to our first case of a dangling pointer! In our
getPointer function we create
someInt. This initialises some memory on the stack to be the value 5. Then we create a pointer to this value and return it — The pointer being the “address” in memory where the value is stored. The problem with this is that at the end of a variables scope it gets “dropped”. This means that the place in memory where it was being stored is cleared. So now any pointer to that memory is left “dangling” and not pointing to anything. In this case, the
getPointer function is the scope of
This is dangerous because of a combination of two things. Firstly, it means that the values we are expecting won’t be there. And secondly, due to the nature of this bug we won’t be able to discover it until it’s too late; At runtime.
Now that we know the problem, let’s look at the same example but written in Rust. This will show us how Rust solves the problem:
Rust won’t even compile this code! The compiler is smart enough to figure out what is going on and throws an error telling you how to fix it:
Rust is able to figure out that this code is wrong because of something called
lifetimes. Lifetimes tell the compiler how long each variable “lives” for and where it’s scope ends (and will consequently be dropped). Therefore, it can figure out the dangling pointer bug in our program before runtime and gives us a nice little warning describing the problem. This prevents a lot of some pretty hard to debug problems.
If we want a reference to the
someInt value then we can instead use one of Rust’s smart pointers called
Rc (short for reference counter).
Rc will only drop
someInt if there are 0 references to it. This means that we can safely return a reference to
someInt even if
someInt goes out of scope — Giving us our intended behaviour in a safe way:
Dangling pointers are pointers that point to an address in memory that is no longer being utilised — It points to a value that has already been dropped. This leads to some problems that are notoriously hard to debug due to the fact that they only crop up at runtime. Rust provides a clean and elegent solution to the problem by discovering the dangling pointers at compile time and warning you.
I think it would be dishonest to not mention some other solutions too; Namely, C++’s smart pointers or Valgrind’s memory checker. But delving into those is outside the scope of this article so I implore you to do some further digging if you are interested.