Understanding Strings in Rust
Tags: rust, tutorial, beginners, programming
Published: 2024-03-23 18:30:00

When working with text in Rust, it's essential to understand the two primary string types: `String` and `str`. In this guide, we'll explore the differences between them and how to use them effectively.

## Introduction to Strings

A string is a collection of characters. In Rust, the `String` type represents a growable, mutable, owned, heap-allocated UTF-8 encoded string. On the other hand, `str` (string slice) is an immutable view into a sequence of UTF-8 bytes, usually borrowed from a `String` or a string literal.

## String Type (`String`)

A `String` in Rust is a mutable, UTF-8 encoded string that can grow and shrink in size. You can create a `String` from a string literal or from another `String` using the `to_string` method.

```rust
let s1: String = "Hello, ".to_string();
let s2: String = String::from("world!");
let s3: String = String::new(); // Dynamic empty string
let combined = s1 + &s2;

println!("{}", combined); // Prints: Hello, world!
```

One other way to make a `String` is called `.into()` but it is a bit different because `.into()` isn't just for making a `String`.

```rust
fn main() {
    let s4 = "Try to make this a String".into(); 
    // ⚠️type annotations needed, consider giving `s4` a type

    let s5: String = "Try to make this a String".into();
    // ✅ Now compiler is happy with `String` type
}
```

### Methods on `String`

`String` has various methods for manipulating and working with strings. Some common methods include:

- `push_str`: Appends a string slice to the end of the `String`.
- `replace`: Replaces a substring with another substring.
- `to_uppercase`/`to_lowercase`: Converts the string to uppercase or lowercase.

```rust
let mut my_string = String::from("Hello, world!");
my_string.push_str(" How are you?");
my_string = my_string.replace("world", "Rust");

println!("{}", my_string.to_uppercase()); 
// Prints: HELLO, RUST! HOW ARE YOU?
```

## String Slice (`&str`)

A `str` is an immutable reference to a sequence of UTF-8 bytes. It is usually seen as a borrowed form of a `String` or a string literal.

```rust
let s: &str = "Hello, world!";
let slice: &str = &s[0..5]; // Take a slice from index 0 to 5 (exclusive)

println!("{}", slice); // Prints: Hello
```

You can even write emojis, thanks to UTF-8.

```rust
fn main() {
    let name = "😂";
    println!("My name is actually {}", name);
    // prints: My name is actually 😂
}
```

### Methods on `str`

`str` also has a variety of methods for manipulating and working with text. Some common methods include:

- `len`: Returns the length of the string.
- `is_empty`: Returns true if the string is empty.
- `starts_with`/`ends_with`: Checks if the string starts or ends with a given substring.

```rust
let my_str: &str = "Hello, Rust!";
let my_string: String = String::from("Hello, Rust!");

println!("str Length: {}", my_str.len());
println!("String Length: {}", my_string.len());

println!("str Is empty: {}", my_str.is_empty());
println!("String Is empty: {}", my_string.is_empty());

println!("str Starts with 'Hello': {}", my_str.starts_with("Hello"));
println!("String Starts with 'Hello': {}", my_string.starts_with("Hello"));
```

## Transforming Between `String` and `str`

You can convert a `String` to a `&str` using the `as_str` method or by using the `&` operator. Conversely, you can convert a `&str` to `String` using the `to_string` method.

```rust
let my_string: String = String::from("Hello");
let my_str1: &str = my_string.as_str();
let my_str2: &str = &my_string;

println!("{}", my_str1); // Prints: Hello
println!("{}", my_str2); // Prints: Hello

let my_str: &str = "World!";
let my_string: String = my_str.to_string();

println!("{}", my_string); // Prints: World!
```

## Concatenating Strings

There are several ways to concatenate strings in Rust, depending on your needs:

- Using the `+` operator (consumes ownership of the first string).
- Using the `format!` macro (does not consume ownership of any string).
- Using the `push_str` method to append a string slice to a `String`.
- Using the `push` method to append a single character or string slice to a `String`.

```rust
let s1 = String::from("Hello, ");
let s2 = String::from("world!");

let combined = s1 + &s2; // Using the + operator

let combined = format!("{}{}", s1, s2); // Using the format! macro

let mut s1 = String::from("Hello,");
let s2 = String::from("world!");

s1.push_str(&s2); // Using the push_str method

let mut s1 = String::from("Hello,");
let s2 = String::from("world!");

s1.push(' '); // Adding a space(' ') Char type
s1.push_str(&s2); // Using the push method
```

When concatenating strings, be aware of ownership and borrowing rules to avoid issues like moving ownership unintentionally. If you need to concatenate strings in a loop or multiple times, consider using a `String` and appending with `push_str` for better performance.

## Conclusion

Understanding the differences between `String` and `str` is crucial for writing efficient and safe Rust code when working with strings. By following Rust's ownership and borrowing rules, you can manipulate strings effectively while ensuring memory safety.