Static and Dynamic Typing

ancient stone monuments

Today we're talking about the difference between static and dynamic typing in our programming languages.

Ruby is a dynamically typed language, and Rust is a statically typed language. This all revolves around how we handle data types in our code.

We know that generally, different kinds of data in computing have different kinds of types. We generally store text in strings, numbers as integers or floats, or perhaps something else depending on the number and the programming language. We have booleans, and more.

Dynamically typed languages are defined by when we check the types of our data. If this type-checking happens when we run the program, we can say that the language is dynamically typed. In general, our interpreted languages are dynamic in nature. And as a Rubyist, this sounds all normal to us.

string = "Hello World"
number = 8675309
condition = true

Look at this code, we've all done something similar to us. We're going to use a value and we dump it into a variable.

We can also do something like this:

something = ["I", "Am", "An", "Array", "Of", "Strings"]
something = "Not anymore, you aren't."

The first thing we've done here is that we've stored an array of strings in our variable, something. And then immediately afterwards, we are going to put a string in there. Ruby lets us do this sans complaints.

This is what we mean when we say that Ruby is a dynamically typed language. The types of the variables are checked at runtime. We see here that the language determines what data type the variable is by seeing what is inside of it when it runs. We never have to declare the data type of each variable when writing code.

The benefit of this is that we don't have to go through the rigamarole of declaring what data type our variable is going to be when we first use it. The ability to change what data type a variable is also gives us a bit more flexibility in this regard.

Let's now contrast that with static typing. You may have been able to deduce that if dynamically typed languages do their type checking during runtime, that statically typed languages do their type checking during compilation.

Practically, this means that when we use a variable, we have to declare what type the variable will be. And so, when we write our code like this, we can't just put any sort of data in any variable that we choose, like we did above with Ruby.

fn main() {
    let mut var = 123;
    var = "hello";
}

This example in Rust has us setting a variable equal to the integer 123, and then changes it to a string hello. let is the keyword that says that says, "Hey we're going to declare a variable here." Technically we can say that it is binding a value to a variable, it is introducing a new variable to the current scope.  You can ignore the mut for now; this lets us change the contents of the variable at a later date. We will have a whole discussion about Rust and Mutability later on.

Right after we assign 123 to the variable var we immediately try to set var to the string "hello".  And two things will happen.

The first is that we got some squiggly lines here. I use Visual Studio Code as my editor and I have the Rust extension installed and immediately it is not happy with the code I have written. If we mouse over, my computer is going to be incredibly helpful and tell us what it's complaining about.

Look at this - we have a mismatched type error. It was expecting an integer and it found a string. Some would call this a less than optimal situation.

But what happens if I just try to ignore all warnings and try to compile it?

And it won't let me. We have a little more detail here - we're expecting a i32 number, but instead we got a measly old string.

So after we've created this variable var and stored 123 in it, you might say, hol' up. I thought the point of statically typed languages was that we had to say right out what data type variables are going to be. Looking at this code, shouldn't we be telling the compiler somewhere that we expect the var variable to hold an i32 integer?

We kind of did though. By storing an three digit integer in var, Rust will assume that we want to use an i32 data type. (We'll talk about all of the data types later) And because Rust is going to assume that we are going to only use var for integers, when we later attempt to assign the string "hello" to it, it refuses. It tells us that it was looking for the value we wanted to put into the variable to be an i32, but instead all it got was a string.

But why do we bother doing this? There's a couple of reasons. A big part of it lies around memory management, and this is adjacent to why we even have data types in the first place.

When we use a variable in our code, we want to store some sort of value in it. And so, our computer saves some space in our RAM (Random Access Memory) that it's going to keep the value of the variable in. And it's here that we want to be efficient. RAM is relatively expensive, and so we want to make the most of what we have. We know that data can be all sizes. So when we want to store a string, the computer saves a certain amount of memory. When we want to use a number it saves, or allocates another portion of memory that's a certain size for us to use.

With a statically typed language, when we use a variable and declare a data type for it, sets aside a specific place in our memory for it, and even if we change what the variable contains, it will still be of the same data type, and we can continue to use that original location or address in memory to store our information.

However, with a dynamically typed language - it's dynamic. More things can happen. We'll just use arbitrary numbers here. We assign an integer to a variable. It uses 64 bits. That's great - we've found a slot that has 64 bits available. And now we are going to replace that with a string that's much longer. Let's presume that the string is larger in size than the 64 bits we were using for that number. Well, that's certainly not going to fit in our previous space. So now, the computer needs to find a new spot for this data to live that is the sufficient size, allocate the space, and then de-allocate the old space. There's a number of additional steps here, making it a slower process.

There's not a better or worse way to do it. One is not better than the other. Not having to worry about static typing in Ruby is nice. You write code faster. However, this is one of the reasons why dynamically typed languages, such as Ruby, are not as performant. And again - this is not a bad thing. We know that Ruby was designed to be as human friendly as possible. And that's the trade off between the two - ease of use versus performance.

Subscribe to Rust for Rubyists

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe