10 Rustlings Strings Solution

From the ReadMe:

Rust has two string types, a string slice (&str) and an owned string (String). We're not going to dictate when you should use which one, but we'll show you how to identify and create them, as well as use them.

Further information

strings1.rs

// strings1.rs
// Make me compile without changing the function signature!
// Execute `rustlings hint strings1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let answer = current_favorite_color();
    println!("My current favorite color is {}", answer);
}

fn current_favorite_color() -> String {
    "blue"
}

Our instructions are pretty straight forward we must make this code compile without changing the function signature. As always we'll take a look at the errors to and see if we get any hints.

strings1.rs errors

⚠️  Compiling of exercises/strings/strings1.rs failed! Please try again. Here is the output:
error[E0308]: mismatched types
  --> exercises/strings/strings1.rs:13:5
   |
12 | fn current_favorite_color() -> String {
   |                                ------ expected `String` because of return type
13 |     "blue"
   |     ^^^^^^- help: try using a conversion method: `.to_string()`
   |     |
   |     expected struct `String`, found `&str`

error: aborting due to previous error

We do get a hint, it tells us to try using a conversion method: .to_string(), so why not let's try that.

strings1.rs solution

fn main() {
    let answer = current_favorite_color();
    println!("My current favorite color is {}", answer);
}

fn current_favorite_color() -> String {
    "blue".to_string()
}

Just like this, it works appending our "blue" string with .to_string converts our &str to a String.

Converting &str to String with .to_string(): The method .to_string() is used to convert a &str (string slice) to an owned String. In Rust, &str represents a string slice, which is an immutable reference to a string. A &str is a view into a string, and is usually used in program arguments and for reading strings from files or other I/O operations. On the other hand, String is a growable, mutable, owned, heap allocated data structure. When you call .to_string() on a &str, you essentially allocate a chunk of heap memory, copy the contents of the &str to that newly allocated memory, and return a String that owns that heap memory. This is why "blue".to_string() gives you an owned String.

This is the output:

Output:
====================
My current favorite color is blue

====================

strings2.rs

// strings2.rs
// Make me compile without changing the function signature!
// Execute `rustlings hint strings2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let word = String::from("green"); // Try not changing this line :)
    if is_a_color_word(word) {
        println!("That is a color word I know!");
    } else {
        println!("That is not a color word I know.");
    }
}

fn is_a_color_word(attempt: &str) -> bool {
    attempt == "green" || attempt == "blue" || attempt == "red"
}

We have another exercise that asks for a similar request: compile without changing the function signature!

strings2.rs errors

⚠️  Compiling of exercises/strings/strings2.rs failed! Please try again. Here is the output:
error[E0308]: mismatched types
  --> exercises/strings/strings2.rs:9:24
   |
9  |     if is_a_color_word(word) {
   |        --------------- ^^^^
   |        |               |
   |        |               expected `&str`, found struct `String`
   |        |               help: consider borrowing here: `&word`
   |        arguments to this function are incorrect
   |
note: function defined here
  --> exercises/strings/strings2.rs:16:4
   |
16 | fn is_a_color_word(attempt: &str) -> bool {
   |    ^^^^^^^^^^^^^^^ -------------

So, we're being told a couple of things, that the arguments in our function are incorrect and that the the compiler is expecting a &str but it's finding a String instead. So let's try making it an &str by adding a reference to word.

strings2.rs solution

fn main() {
    let word = String::from("green"); // Try not changing this line :)
    if is_a_color_word(&word) { // added `&` to `word`
        println!("That is a color word I know!");
    } else {
        println!("That is not a color word I know.");
    }
}

fn is_a_color_word(attempt: &str) -> bool {
    attempt == "green" || attempt == "blue" || attempt == "red"
}

The & operator to create a reference: The & operator in Rust is used to create a reference to a value. When you write &word in the call to is_a_color_word(&word), you are passing a reference to word rather than word itself. This is important because the function is_a_color_word is expecting a &str (a string slice), not a String. If you tried to pass word directly, Rust would raise a type mismatch error because word is a String and not a &str. By using &, you're allowing is_a_color_word to borrow word as a &str for the duration of the function call.

With that we're compiling here too, simple enough, we get the following print out:

Output:
====================
That is a color word I know!

====================

If we change the line let word = String::from("red") or "blue" we'd continue to get the same printout but, if we change it say to purple we get this:

Output:
====================
That is not a color word I know.

====================

strings3.rs

// strings3.rs
// Execute `rustlings hint strings3` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn trim_me(input: &str) -> String {
    // TODO: Remove whitespace from both ends of a string!
    input.().to_string()
}

fn compose_me(input: &str) -> String {
    // TODO: Add " world!" to the string! There's multiple ways to do this!
    let mut result = input.to_owned();
    result.push_str(" world!");
    result
}

fn replace_me(input: &str) -> String {
    // TODO: Replace "cars" in the string with "balloons"!
    input.replace("cars", "balloons")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn trim_a_string() {
        assert_eq!(trim_me("Hello!     "), "Hello!");
        assert_eq!(trim_me("  What's up!"), "What's up!");
        assert_eq!(trim_me("   Hola!  "), "Hola!");
    }

    #[test]
    fn compose_a_string() {
        assert_eq!(compose_me("Hello"), "Hello world!");
        assert_eq!(compose_me("Goodbye"), "Goodbye world!");
    }

    #[test]
    fn replace_a_string() {
        assert_eq!(replace_me("I think cars are cool"), "I think balloons are cool");
        assert_eq!(replace_me("I love to look at cars"), "I love to look at balloons");
    }
}

So here we have to make our code compile and pass the tests, we get a list of TODO items on how to finish our functions.

strings3.rs errors

⚠️  Compiling of exercises/strings/strings3.rs failed! Please try again. Here is the output:
error: expected expression, found `?`
 --> exercises/strings/strings3.rs:8:5
  |
8 |     ???
  |     ^ expected expression

error: expected expression, found `?`
  --> exercises/strings/strings3.rs:13:5
   |
13 |     ???
   |     ^ expected expression

error: expected expression, found `?`
  --> exercises/strings/strings3.rs:18:5
   |
18 |     ???
   |     ^ expected expression

error: aborting due to 3 previous errors

Our errors are clearly due to the missing function bodies.

strings3.rs solution

fn trim_me(input: &str) -> String {
    // TODO: Remove whitespace from both ends of a string!
    input.trim().to_string()
}

fn compose_me(input: &str) -> String {
    // TODO: Add " world!" to the string! There's multiple ways to do this!
    let mut result = input.to_string();
    result.push_str(" world!");
    result
}

fn replace_me(input: &str) -> String {
    // TODO: Replace "cars" in the string with "balloons"!
    input.replace("cars", "ballons")
}

To pass the test and finalize the functions we have to use methods on each of the input parameters that are being passed through.

In our first function trim_me as the comment states we have to remove whitespace from the string. The trim() method does just that and we can use .to_string to convert our parameter input from a &str to a String.

In the compose_me there are a few different ways to solve this, I chose to create a new variable result that contains the input.to_string() and then we use the push_str method on result to add the "world!" to our newly created String. Then finally return the result.

Finally in our replace_me function we use the replace() method to simply replace "cars" to: "balloons" and that should do it.

strings4.rs

// strings4.rs

// Ok, here are a bunch of values-- some are `String`s, some are `&str`s. Your
// task is to call one of these two functions on each value depending on what
// you think each value is. That is, add either `string_slice` or `string`
// before the parentheses on each line. If you're right, it will compile!
// No hints this time!

// I AM NOT DONE

fn string_slice(arg: &str) {
    println!("{}", arg);
}
fn string(arg: String) {
    println!("{}", arg);
}

fn main() {
    &str("blue");
    ???("red".to_string());
    ???(String::from("hi"));
    ???("rust is fun!".to_owned());
    ???("nice weather".into());
    ???(format!("Interpolation {}", "Station"));
    ???(&String::from("abc")[0..1]);
    ???("  hello there ".trim());
    ???("Happy Monday!".to_string().replace("Mon", "Tues"));
    ???("mY sHiFt KeY iS sTiCkY".to_lowercase());
}

Alright this seems easy enough we have to understand if the value is a &str or a String

strings4.rs errors

⚠️  Compiling of exercises/strings/strings4.rs failed! Please try again. Here is the output:
error: expected expression, found `?`
  --> exercises/strings/strings4.rs:19:5
   |
19 |     ???("blue");
   |     ^ expected expression

error: aborting due to previous error

We have one error, but it is obvious that we'll have a few more after where we have the question marks.

strings4.rs solution

fn string_slice(arg: &str) {
    println!("{}", arg);
}
fn string(arg: String) {
    println!("{}", arg);
}

fn main() {
    string_slice("blue");
    string("red".to_string());
    string(String::from("hi"));
    string("rust is fun!".to_owned());
    string_slice("nice weather".into());
    string(format!("Interpolation {}", "Station"));
    string_slice(&String::from("abc")[0..1]);
    string_slice("  hello there ".trim());
    string("Happy Monday!".to_string().replace("Mon", "Tues"));
    string("mY sHiFt KeY iS sTiCkY".to_lowercase());
}

Choosing between string_slice and string in strings4.rs: The string_slice function takes a &str (string slice) argument, while string function takes a String argument. When you're calling these functions in main(), you need to determine whether each argument is a &str or a String.

  • For instance, "blue" is a &str, because it's a string literal. In Rust, string literals are string slices.

  • "red".to_string(), on the other hand, is a String. As explained earlier, .to_string() is a method that converts a &str to a String by allocating heap memory and copying the string there.

  • String::from("hi") is also a String. String::from is a function that performs the same task as .to_string(), creating an owned string from a string literal or a string slice.

  • "nice weather".into() is an interesting case. The .into() method is a very generic conversion method that converts between various types. In this context, it will automatically be resolved to String, so it should be passed to string function. However, it's worth noting that this automatic resolution is based on the current context and it can lead to potential confusion if the type resolution is ambiguous.

Conclusion

Understanding how to work with strings is essential in Rust, as it's a common part of most programming tasks. We've learned that Rust has two main string types: &str and String. The &str type is a string slice, which is an immutable reference to a string, whereas String is an owned string that can be modified.

We've also practiced converting between these two types using the to_string() method, and we've learned how to create references to strings using the & operator. Additionally, we've used various methods to manipulate strings, such as trim(), push_str(), and replace().

The exercises and solutions provided in this post are a good starting point for understanding and mastering strings in Rust. Continue experimenting and practicing with these concepts to gain a solid understanding of how strings work in Rust.