DEV Community

Cover image for [Rust Guide] 8.3. String Type Pt.1 - String Creation, Updating, and Concatenation
SomeB1oody
SomeB1oody

Posted on

[Rust Guide] 8.3. String Type Pt.1 - String Creation, Updating, and Concatenation

8.3.0. Chapter Overview

Chapter 8 is mainly about common collections in Rust. Rust provides many collection-like data structures, and these collections can hold many values. However, the collections covered in Chapter 8 are different from arrays and tuples.

The collections in Chapter 8 are stored on the heap rather than on the stack. That also means their size does not need to be known at compile time; at runtime, they can grow or shrink dynamically.

This chapter focuses on three collections: Vector, String (this article), and HashMap.

If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.

8.3.1. Why Strings Are So Frustrating for Rust Developers

Rust developers, especially beginners, are often confused by strings for the following reasons:

  • Rust tends to expose possible errors.
  • String data structures are complex.
  • Rust strings use UTF-8 encoding.

8.3.2. What Is a String?

A string is a collection based on bytes, and it provides methods that can parse bytes into text.

At the core language level in Rust, there is only one string type: the string slice str, which usually appears in borrowed form, that is, &str.

A string slice is a reference to a UTF-8 encoded string stored somewhere else. For example, string literals are stored directly in Rust’s binary, so they are also a kind of string slice.

The String type comes from the standard library, not from the core language. It is growable, mutable, owned, and also uses UTF-8 encoding.

8.3.3. What Does “String” Actually Refer To?

When people say “string,” they usually mean both String and &str, not just one of them. Both types are heavily used in the standard library, and both use UTF-8 encoding. But here we mainly focus on String, because it is more complex.

8.3.4. Other String Types

The Rust standard library also provides other string types, such as OsString, OsStr, CString, and CStr. Note that these types all end with either String or Str, which is related to the naming pattern of String and string slices mentioned earlier.

In general, types ending with String are owned, while types ending with Str are usually borrowed.

These different string types can store text with different encodings or represent data in different memory layouts.

Some library crates provide more options for strings, but we will not cover them here.

8.3.5. Creating a New String

Because the essence of String is a collection of bytes, many operations from Vec<T> can also be used on String.

String::new() can be used to create an empty string. Example:

fn main(){
    let mut s = String::new();
}
Enter fullscreen mode Exit fullscreen mode

In general, however, String is created from initial values. In that case, you can use the to_string method to create a String. This method can be used on types that implement the Display trait, including string literals. Example:

fn main() {  
    let data = "wjq";  
    let s = data.to_string();  
    let s1 = "wjq".to_string();  
}
Enter fullscreen mode Exit fullscreen mode

data is a string literal. Using to_string converts it to a String and stores it in s. You can also write the string literal directly and then call .to_string(), which is the assignment performed for s1. These two operations have the same effect.

to_string is not the only method. Another way is to use the String::from function:

let s = String::from("wjq");
Enter fullscreen mode Exit fullscreen mode

This function has the same effect as the to_string method.

Because strings are used so often, Rust provides many different general-purpose APIs for us to choose from. Some functions may seem redundant at first glance, but in practice they each have their own use. In real code, you can choose whichever style you prefer.

8.3.6. Updating String

As mentioned earlier, the size of String can grow or shrink. Because its essence is a collection of bytes, its contents can also be modified. Its operations are similar to those of Vector, and String can also be concatenated.

1. push_str()

First, let’s look at push_str(). It appends a string slice to a String. Example:

fn main() {  
    let mut s = String::from("6657");  
    s.push_str("up up");  
    println!("{}", s);  
}
Enter fullscreen mode Exit fullscreen mode

Output:

6657up up
Enter fullscreen mode Exit fullscreen mode

The signature of push_str is push_str(&mut self, string: &str). Its parameter is a borrowed string slice, and a string literal is a slice, so "up up" can be passed in. This method does not take ownership of the argument, so the passed-in value remains valid and can continue to be used.

2. push

The second method is push(), which appends a single character to a String. Example:

fn main() {  
    let mut s = String::from("665");  
    s.push('7');  
    println!("{}", s);  
}
Enter fullscreen mode Exit fullscreen mode

Note: characters must use single quotes.

Output:

6657
Enter fullscreen mode Exit fullscreen mode

3. +

Rust allows you to concatenate strings using +. Example:

fn main() {  
    let s1 = String::from("6657");  
    let s2 = String::from("up up");  
    let s3 = s1 + &s2;  
    println!("{}", s3);  
}
Enter fullscreen mode Exit fullscreen mode

Note: the value before the plus sign must be a String, and the value after the plus sign must be a string slice.

In this example, however, the type of the value after the plus sign is actually &String, not &str. That is because Rust uses deref coercion here to force &String into &str.

Of course, because s2 is passed in by reference, s2 is still valid after concatenation. But s1 has had its ownership moved into s3, so s1 becomes invalid after concatenation.

Output:

6657up up
Enter fullscreen mode Exit fullscreen mode

4. format!

The format! macro can concatenate strings more flexibly. Example:

fn main() {  
    let s1 = String::from("cn");  
    let s2 = String::from("Niko");  
    let s3 = String::from("fan club");  
    let s = format!("{} {} {}", s1, s2, s3);  
    println!("{}", s);  
}
Enter fullscreen mode Exit fullscreen mode

It uses placeholders instead of variables, which is very similar to println!. The difference is that println! prints the result, while format! returns the concatenated string.

Output:

cn Niko fan club
Enter fullscreen mode Exit fullscreen mode

Of course, the same effect can also be achieved with +, but the code is a little more cumbersome:

fn main() {  
    let s1 = String::from("cn");  
    let s2 = String::from("Niko");  
    let s3 = String::from("fan club");  
    let s = s1 + " " + &s2 + " " + &s3;  
    println!("{}", s);  
}
Enter fullscreen mode Exit fullscreen mode

The best thing about format! is that it does not take ownership of any arguments, so all of those arguments can continue to be used afterward.

Top comments (0)