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:
![](https://i0.wp.com/ezesunday.com/blog/wp-content/uploads/2024/05/image-4.png?resize=800%2C117&ssl=1)
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"
}
![](https://i0.wp.com/ezesunday.com/blog/wp-content/uploads/2024/05/image-1.png?resize=800%2C360&ssl=1)
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"
}
![](https://i0.wp.com/ezesunday.com/blog/wp-content/uploads/2024/05/image-2.png?resize=800%2C361&ssl=1)
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"
}
![](https://i0.wp.com/ezesunday.com/blog/wp-content/uploads/2024/05/image-3.png?resize=800%2C361&ssl=1)
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