Rust string manipulations

Rust string manipulations

Strings are the building blocks of text data, and being able to manipulate them efficiently is a crucial. 

In this article, we’ll explore the different ways to manipulate a string in Rust with examples.

There are two types of strings in Rust: string slice and String

  • A string slice (str) is a reference to a string or part of a string. It is stored in the program’s executable and is immutable
  • A String is a mutable string type in Rust and it’s stored on the heap.

Let’s start with concatenation…

Concatenation

Try this code out on Rust Playground:

fn main() {
    let new_string = "Hello" + ", world!";
     println!("{}", new_string);
}

You’ll get an error like this one:

While this is a common method to concatenate strings in other languages, Rust is surprisingly different. You can’t use the above method to concatenate the above string because it’s a type of string slice known as a string literal.

The plus operator trait—the Add trait—is not implemented for string slices for concatenation. However, you can do something like this:

let new_string = String::from("Hello") + ", world!";

And it works perfectly because the plus operator trait—the Add trait—is implemented for String in a way that allows string literals on the right-hand side to be appended to the string on the left-hand side, as seen above. Here is the implementation of the Add trait from the source code

#[cfg(not(no_global_oom_handling))]

#[stable(feature = "rust1", since = "1.0.0")]

impl Add<&str> for String {

    type Output = String;

    #[inline]

    fn add(mut self, other: &str) -> String {

        self.push_str(other);

        self

    }

}

As you can see from the original implementation of the Add trait for the String type, it only allows you to append a string slice type to the String type. So, anything other than that will throw an error.

That being said, if you reverse the order of the concatenation this way, it won’t work; it will throw an error:

fn main() {

   let new_string = "Hello" + ", world!".to_string();

    println!("{}", new_string);

}

You can also use the .to_owned() method to convert the literal string to a String, depending on the scenario and then go ahead to go ahead to concat it with the literal:

fn main() {

   let new_string: String = "Hello".to_owned() + ", world!";

    println!("{}", new_string);

}

We can also use the .into() method as shown below to convert the first string slice to a String

fn main() {

let new_string: String = <&str as Into<String>>::into("Hello") + ", world!";

    println!("{}", new_string);

}

However, we can’t concatenate two Strings like "Hello".to_string() + ", world!".to_string()

fn main() {

   let new_string: String = "Hello".to_owned() + ", world!".to_owned(); // Error
   
   //or
   
   let new_string2: String = "Hello".to_string() + ", world!".to_string(); // Error

    println!("{}", new_string);

}

It will throw an error because the Add trait was only implemented to append a string slice to a String as we mentioned earlier.

Other ways to concatenate a string

Using the format! macro:

let result = format!("{}{}", "Hello", ", world!");

Using the concat! macro:

let result = concat!("Hello", ", world!");

Using push_str method like so:

let mut s = String::from("Hello");

s.push_str(",world!");

Slicing

Slicing a string is a way to extract a subset of characters from a string. Say you want to extract Hello from the string Hello world. There are several ways to slice a string in Rust but you can’t just index it as shown below due to some ambiguities related to UTF-8 character encoding.

Rust strings store characters using UTF-8 encoding. UTF-8 uses a variable number of bytes to represent a single character. This means a single index might not necessarily correspond to a single character.

For example, the symbol in UTF-8 encoding takes up 3 bytes. If you indexed s[0] on a string "€17", you might only get the first byte, which wouldn’t represent the entire character ‘‘ which is why indexing a string this way s[0] is forbidden in Rust:

fn main() {
    let s = "Hello, world!";
    let slice = &s[0]; 
    println!("{}", slice);
}
Using Indexing

string[start_index..end_index] This extracts a slice from the start_index (inclusive) up to, but not including, the end_index as shown in the example below:

fn main() {
    let s = "Hello, world!";
    let slice = &s[0..5]; 
    println!("{}", slice); // "Hello"
}

There is also another way to index a string by splitting its characters and returning the nth index value:

fn main() {
    let string  = "Hello, world";
    println!("{:?}", string.chars().nth(0).unwrap()); // 'H'
}
Using skip and take

Use the chars method to iterate over the string and use skip to define the start index of the substring and take to define the end index of the substring as shown below:


fn main() {
    let s = "Hello, world";
    let slice: String = s.chars().skip(7).take(12).collect();
    println!("{}", slice); // "world"
}
Using the get method

You can also use the .get method to slice a string and get part of it as shown below. .get returns a result, so you might want to unwrap it to get the actual value.


fn main() {
    let s = "Hello, world";
    let slice = s.get(7..12);
    println!("{}", slice.unwrap()); // world
}

Adding and removing a character from the end of a string

In a string, the push and pop methods are used to add a single character to the end of a string and remove a character from the end of a string, respectively.

.push method

The .push method doesn’t return the pushed in character.

fn main() {
    let mut string  = "Hello, world".to_string();
    string.push('r');
    println!("{}", string); // Hello, worldr
}
.Pop method

The pop method returns an a Result which contains the popped slice of the string.

fn main() {
    let mut string  = "Hello, world".to_string();
    let popped = string.pop();
    println!("{}", popped.unwrap()); // d
}

Insert and Remove a character from the beginning of string

Unlike pop and push that focuses on the last characters of a string, the .insert and .remove focuses on the beginning part of a string. .insert pushes a character to the beginning of a string while the .remove method removes a character from the beginning of a string.

fn main() {
    let mut s = String::from("ello");
    s.insert(0, 'H'); // inserts "H" at the beginning
    println!("{}", s); // "Hello"

    s.remove(0); // removes the first character
    println!("{}", s); // "ello"
}

Removing whitespaces from the beginning of a string

The .trim method helps with removing whitespaces from the beginning and end of a string. Here is an example:


fn main() {
    let s = String::from("          hello world     ");
    println!("{}", s.trim()); // "hello world"
}

Convert a string to upper case and lower case

Use the .to_lowercase method to convert a string to lower case letters and the .to_uppercase to make the conversions.

fn main() {
    let s = String::from("HELLO");
    println!("{}", s.to_lowercase()); // "hello"

    let t = String::from("hello");
    println!("{}", t.to_uppercase()); // "HELLO"
}

Splitting a string into substrings

We can use the .split() method to split a string into parts and join them back together using the .join() method. Here is an example:

fn main() {

    let s = "hello,world,rust";
    let substrings: Vec<&str> = s.split(",").collect(); // splits into substrings
    println!("{:?}", substrings); // ["hello", "world", "rust"]

    let joined = substrings.join(","); // joins substrings back together
    println!("{}", joined); // "hello,world,rust"
}

Check a substring exists in a string

We can use the .contains to check if a string contains a specific substring. Use the .starts_with() to check that a string starts with a specific substring. And also use the ends_with() to check that a string ends with a specific substring.




fn main() {

    let s = "hello, world";
    if s.contains("world") { // checks if "world" is in the string
        println!("'world' is in the string");
    }

    if s.starts_with("hello") { // checks if the string starts with "hello"
        println!("the string starts with 'hello'");
    }

    if s.ends_with("world") { // checks if the string ends with "world"
        println!("the string ends with 'world'");
    }
}

Replacing a string

The .replace method is used to replace a string with another string

fn main() {
    let s = String::from("hello world");
    println!("{}", s.replace("world", "rust")); // "hello rust"
}

Happy hacking

Buy Me A Coffee

Published by Eze Sunday Eze

Hi, welcome to my blog. I am Software Engineer and Technical Writer. And in this blog, I focus on sharing my views on the tech and tools I use. If you love my content and wish to stay in the loop, then by all means share this page and bookmark this website.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.