13 Jan 25

Fascination Street - Jan 13, 2025

Writing for the week of Jan 13, 2025

This week’s post is going to be technical. It covers some details of the Rust programming language. I’ve been working with Rust a lot lately. Working on backend services and a frontend delivered via WASM.

One of the really interesting aspects of the language is the handling of Strings. Strings by themselves are some of the most basic parts of programming languages. In simple terms, a string in most languages is simply a group of characters. “hello” is an example of string.

However, in Rust, Strings are majorly impacted by memory, ownership, and safety. There are actually three different versions of Strings. Kind of. I’ll get to the part that fascinated me this week at the end. Before that, I’ll explain the three versions as I understand them and why they exist.

Rust “stores” data in three places (in memory) during the execution of a script. We’ll call them the Stack, the Heap, and Data. These are not unique to Rust, other runtimes do something similar. Data is where the raw group of characters goes in the most simple terms. The Stack and Heap have very important responsibilities providing the location and value of strings.

In today’s example, we are going to use the string “hello”. When our Rust program first sees that string, a few things happen according to my understanding. First, the group of characters gets allocated in the Data space. Next, the value is copied to the Heap and we keep track of the path.

Next, a structure gets defined in the Stack. The structure contains three piece of information. The first is the pointer to the value in the Heap. The second is the length of the string, in this case 5. Last, is the capacity of the string. In this case that is also 5. For now, we’ll just assume the string is going to stay the same and not grow or shrink.

It looks something like this:

Stack

String Struct {

pointer to text: points to location in the heap,

length of string: 5,

capacity of string: 5

}

Heap

“hello”

Data

“hello”

That’s pretty high level and a simple generalization of what is happening. It provides enough context for what we want to walk through below.

Basic String (aka String)

In Rust, to define a string, we use the String type. For example:

let greeting: String = String::from("hello");

That is the most basic version of a group of characters in Rust. Remember what gets placed on the Stack, Heap, and Data.

String Reference (aka &String)

The next string type is a read-only reference to a(nother) string. For example:

let greeting: String = String::from("hello"); let greeting_ref = &greeting;

In this case, we’ll have the allocations on the Stack, Heap, and Data for the original group of characters. And we’ll also have a “reference” Struct in the Stack. The reference is a different, but pretty similar structure. It includes a pointer to the allocation in the Stack of the original string. You can understand how that applies to ownership. A basic string can’t go out of scope if it has references because it would orphan the reference. NullPointer anyone? Essentially, if the original string gets clean up, including the structure in the Stack and value in the Heap, any reference would be pointing at a structure that no longer exists, and assumes that structure’s pointer to the Heap.

String slice (aka &str)

The next and final string type is referred to as a string slice. For example:

let greeting: &str = "hello";

In this case, we’ll have an allocation on the Stack and value only in Data. No Heap! The structure in the Stack is slightly different. It contains a pointer to the text and the length of the string. The big difference is that the pointer goes directly to the value in Data, rather than a Heap allocation. Performance is impacted. We don’t grow our Heap.

Caveats

This is the fascinating bits. Not all Slices are created the same and this makes a big difference. Imagine the following where we define a String slice by casting a previously defined String.

let greeting: String = String::from("hello"); let greeting_slice: &str = greeting.as_str();

In this case, we declare the original basic String which creates the structure in the Stack etc. Now second, we declare a String slice using as_str(), but we base it on the original String. Rather than repeating the value in Data and pointing to it, our slice structure in the Stack points to the allocation in the Heap of the original String. Again, the ownership model comes into view.

Ok, that’s interesting. Here’s another caveat.

Take the following example where we define a function and assign three parameters with types:

fn make_sense_of_strings(a: String, b: &String, c: &str) {
  println!("String = {}", a);
  println!("&String = {}", b);
  println!("&str = {}", c);
}

fn main() {
  make_sense_of_strings(
    String::from("hello"),
    &String::from("hello"),
    "hello"
  );

  make_sense_of_strings(
    String::from("world"),
    &String::from("world"),
    &String::from("world")
  );
}

Notice in the second call to make_sense_of_strings the last parameter is a String reference, not a String slice even though the parameter type is declared as a slice! This is valid. The compiler doesn’t shout at you. String references are converted to slices. Makes you start to wonder if all three types are actually needed, or if you should maybe get into the habit of only using two of the three. I think it depends. Readability comes into play. A String reference signals read-only. String slice signals a known length and doesn’t grow the Heap. Like everything there are trade offs.

This was really fun to get into and wrap my head around this week. It’s a fascinating design of the Rust language. I’ll document this in more detail in my upcoming book for Rubyists wanting to learn Rust.

Like always, thanks for reading. Now onto some links

▧ ▧ ▧

Links

  • https://substack.com/home/post/p-143851058 - Written by a Gen Z romanticizing Gen X and Gen Y’s lack of “always connectedness”. I can relate. Thinking back, my high school friends were really close. We hung out. In person. We knew each other because we spent time together. We didn’t sit at home and talk over the internet via XBox or via text message. It was a different time. Some of the things recognized in the post make me sad for today’s teens. I get the sense they don’t have the intimate friendships that I experienced in my youth. The addiction to screens and social media is harming everyone, but especially the youth. Maybe they’ll rebel. There is some research saying dating apps are losing users. Maybe there is hope that people will get out from behind their screens and their fear of missing out will actually turn into going out. I’ve started monitoring my screen time after my son suggested he was mindfully cutting back his time on social media.
  • https://www.youtube.com/watch?v=cIB1b_8hqB0 - David Letterman interviewing Isaac Asmiov from 1980. A few minutes in, he’s asked why movies like Star Wars were suddenly so popular and his answer was simple. The special effects had progressed and people were interested. I can’t help but wonder if that is the case with AI. In today’s capabilities, is AI summarizing or generating based on previous art just special effects?

▧ ▧ ▧

Music

▧ ▧ ▧

A couple of promotions each week. First, use my invite link to try Warp as your terminal. It’s fast and has some great features. I’m not affiliated with them at all, just really like it. Also, check out my project–Schemabook, especially if you work in an organization that wants to get organized around defining data through contracts and collaboration. Lastly, I’m writing a book about learning Rust if you are familiar with Ruby. Stay tuned. As always, you can connect with me more at https://mikekrisher.com.