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 aString
. As explained earlier,.to_string()
is a method that converts a&str
to aString
by allocating heap memory and copying the string there. -
String::from("hi")
is also aString
.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 toString
, so it should be passed tostring
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.