I started programming with Rust this week and I am having a lot of problems understanding how Strings work.
Right now, I am trying to do a simple program that prints a list of players appending their order(for learning purposes only).
let res : String = pl.name.chars().enumerate().fold(String::new(),|res,(i,ch)| -> String {
res+=format!("{} {}\n",i.to_string(),ch.to_string());
});
println!("{}", res);
This is my idea, I know I could just use a for loop but the objective is to understand the different Iterator functions.
So, my problem is that the String concatenation does not work.
Compiling prueba2 v0.1.0 (file:///home/pancho111203/projects/prueba2)
src/main.rs:27:13: 27:16 error: binary assignment operation `+=` cannot be applied to types `collections::string::String` and `collections::string::String` [E0368]
src/main.rs:27 res+=format!("{} {}\n",i.to_string(),ch.to_string());
^~~
error: aborting due to previous error
Could not compile `prueba2`.
I tried using &str but it is not possible to create them from i
and ch
values.
Short answer: no, not that type of interpolation.
The str type, also called a 'string slice', is the most primitive string type. It is usually seen in its borrowed form, &str . It is also the type of string literals, &'static str . Strings slices are always valid UTF-8.
First, in Rust x += y
is not overloadable, so +=
operator won't work for anything except basic numeric types. However, even if it worked for strings, it would be equivalent to x = x + y
, like in the following:
res = res + format!("{} {}\n",i.to_string(),ch.to_string())
Even if this were allowed by the type system (it is not because String + String
"overload" is not defined in Rust), this is still not how fold()
operates. You want this:
res + &format!("{} {}\n", i, ch)
or, as a compilable example,
fn main(){
let x = "hello";
let res : String = x.chars().enumerate().fold(String::new(), |res, (i, ch)| {
res + &format!("{} {}\n", i, ch)
});
println!("{}", res);
}
When you perform a fold, you don't reassign the accumulator variable, you need to return the new value for it to be used on the next iteration, and this is exactly what res + format!(...)
do.
Note that I've removed to_string()
invocations because they are completely unnecessary - in fact, x.to_string()
is equivalent to format!("{}", x)
, so you only perform unnecessary allocations here.
Additionally, I'm taking format!()
result by reference: &format!(...)
. This is necessary because +
"overload" for strings is defined for String + &str
pair of types, so you need to convert from String
(the result of format!()
) to &str
, and this can be done simply by using &
here (because of deref coercion).
In fact, the following would be more efficient:
use std::fmt::Write;
fn main(){
let x = "hello";
let res: String = x.chars().enumerate().fold(String::new(), |mut res, (i, ch)| {
write!(&mut res, "{} {}\n", i, ch).unwrap();
res
});
println!("{}", res);
}
which could be written more idiomatically as
use std::fmt::Write;
fn main(){
let x = "hello";
let mut res = String::new();
for (i, ch) in x.chars().enumerate() {
write!(&mut res, "{} {}\n", i, ch).unwrap();
}
println!("{}", res);
}
(try it on playpen)
This way no extra allocations (i.e. new strings from format!()
) are created. We just fill the string with the new data, very similar, for example, to how StringBuilder
in Java works. use std::fmt::Write
here is needed to allow calling write!()
on &mut String
.
I would also suggest reading the chapter on strings in the official Rust book (and the book as a whole if you're new to Rust). It explains what String
and &str
are, how they are different and how to work with them efficiently.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With