Comparing string in Rust - rust

I want to compare a string input from stdin to a static string with no luck.
Here is what I have tried so far:
fn main() -> () {
let mut line = "".to_string();
let exit = "exit".to_string();
while line.as_slice() != exit.as_slice() {
let input = std::io::stdin().read_line().ok();
line.push_str( input.unwrap().to_string().as_slice() );
assert_eq!(line, exit);
}
}
However during assertion it failed. How should I compare a string input to a static string in Rust?
Your help is very much appreciated.

First of all, the line contains the line terminator. You probably want to use trim (or one of its variants) to ignore that.
Secondly, you're doing a lot of unnecessary conversions and allocations. Try to avoid those.
Third, to_string is (or at least, was the last time I checked) inefficient due to over-allocation. You want into_string.
Fourth, the quickest way to go from a String to a &str is to "cross-borrow" it; given a String s, &*s will re-borrow it as a &str. This is because a String implements Deref<&str>; in other words, String acts kind of like a smart pointer to a borrowed string, allowing it to decay into a simpler form.
Fifth, unless you're doing something unusual, you can rewrite this as a for loop using the lines iterator method.
Sixth, be aware that stdin() actually allocates a new buffered reader every time you call it. Not only that, but characters read into the buffer do not get "pushed back" into STDIN when a new buffer is created; that data is simply lost. So you really don't want to be calling it in a loop. If you need to, call it once and keep the result in a variable.
So, I end up with this:
fn main() {
for line in std::io::stdin().lines() {
// Extract the line, or handle the error.
let line = match line {
Ok(line) => line,
Err(err) => panic!("failed to read line: {}", err)
};
assert_eq!(line.trim(), "exit");
}
}

Related

Why does `format_args!()` ignore truncation? How to truncate without allocation then?

Why does this work?
fn main() {
println!("{:.3}", "this is just a test");
}
prints => thi
While this doesn't?
fn main() {
println!("{:.3}", format_args!("this is just a test"));
}
prints => this is just a test
Here's a playground.
For a little more context, I’m interested in the reasoning behind it, and a way to do it without any allocations.
I'm developing a terminal game in Rust, where I have a write! which shows some statistics about the rendering and game loop, and that text can be quite long. Now that I read the terminal size and adjust its output accordingly, I need to truncate that output, but without any allocations.
I thought I was super clever when I refactored this:
write!(
stdout,
"{} ({} {} {}) {}",
...
)
into this:
write!(
stdout,
"{:.10}", // simulate only 10 cols in terminal.
format_args!(
"{} ({} {} {}) {}",
...
)
)
How unfortunate, it doesn’t work… How to do that without allocating a String?
For one thing, not every type obeys all formatting arguments:
println!("{:.3}", 1024);
1024
Second, format_args! serves as the backbone for all of the std::fmt utilities. From the docs on format_args:
This macro functions by taking a formatting string literal containing {} for each additional argument passed. format_args! prepares the additional parameters to ensure the output can be interpreted as a string and canonicalizes the arguments into a single type. Any value that implements the Display trait can be passed to format_args!, as can any Debug implementation be passed to a {:?} within the formatting string.
This macro produces a value of type fmt::Arguments. This value can be passed to the macros within std::fmt for performing useful redirection. All other formatting macros (format!, write!, println!, etc) are proxied through this one. format_args!, unlike its derived macros, avoids heap allocations.
You can use the fmt::Arguments value that format_args! returns in Debug and Display contexts as seen below. The example also shows that Debug and Display format to the same thing: the interpolated format string in format_args!.
let debug = format!("{:?}", format_args!("{} foo {:?}", 1, 2));
let display = format!("{}", format_args!("{} foo {:?}", 1, 2));
assert_eq!("1 foo 2", display);
assert_eq!(display, debug);
Looking at the source for impl Display for Arguments, it just ignores any formatting parameters. I couldn't find this explicitly documented anywhere, but I can think of a couple reasons for this:
The arguments are already considered formatted. If you really want to format a formatted string, use format! instead.
Since its used internally for multiple purposes, its probably better to keep this part simple; its already doing the format heavy-lifting. Attempting to make the thing responsible for formatting arguments itself accept formatting parameters sounds needlessly complicated.
I'd really like to truncate some output without allocating any Strings, would you know how to do it?
You can write to a fixed-size buffer:
use std::io::{Write, ErrorKind, Result};
use std::fmt::Arguments;
fn print_limited(args: Arguments<'_>) -> Result<()> {
const BUF_SIZE: usize = 3;
let mut buf = [0u8; BUF_SIZE];
let mut buf_writer = &mut buf[..];
let written = match buf_writer.write_fmt(args) {
// successfully wrote into the buffer, determine amount written
Ok(_) => BUF_SIZE - buf_writer.len(),
// a "failed to write whole buffer" error occurred meaning there was
// more to write than there was space for, return entire size.
Err(error) if error.kind() == ErrorKind::WriteZero => BUF_SIZE,
// something else went wrong
Err(error) => return Err(error),
};
// Pick a way to print `&buf[..written]`
println!("{}", std::str::from_utf8(&buf[..written]).unwrap());
Ok(())
}
fn main() {
print_limited(format_args!("this is just a test")).unwrap();
print_limited(format_args!("{}", 123)).unwrap();
print_limited(format_args!("{}", 'a')).unwrap();
}
thi
123
a
This was actually more involved than I originally thought. There might be a cleaner way to do this.
I found this word here
For non-numeric types, this can be considered a "maximum width". If the resulting string is longer than this width, then it is truncated down to this many characters and that truncated value is emitted with proper fill, alignment and width if those parameters are set.
For integral types, this is ignored.
For floating-point types, this indicates how many digits after the decimal point should be printed.
And format_args return type is std::fmt::Arguments,that is not String ,even though it looks like a string.
If you want to get same print contents,i think those code will work
/// unstable
println!("{:.3}", format_args!("this is just a test").as_str().unwrap());
println!("{:.3}", format_args!("this is just a test").to_string().as_str());

Append new line character to Hyper Body struct

I am trying to modify the response that gets returned back from the echo example when using hyper. Their code sample is here.
The only thing that is different in my code is that I am trying to append a new line character to the end of my collection
(&Method::POST, "/echo/uppercase") => {
let newLine = "\n".as_bytes().to_vec();
let m = req.into_body()
.map_ok(|c| {
c.iter()
.map(|byte| byte.to_ascii_uppercase())
.collect::<Vec<u8>>()
.append(&mut newLine) <-- this line
});
*response.body_mut() = Body::wrap_stream(m);
},
The compiler is returning
*response.body_mut() = Body::wrap_stream(m);
^ the trait `std::convert::From<()>` is not implemented for `bytes::bytes::Bytes`
Does the append modify the type of the collection so that it is no longer considered a Future stream for the wrap_stream method?
Also is this the preferred way to append things to a Body struct in hyper?
The closure passed to map_ok() returns (), not the Vec<u8> you are expecting.
This is because Vec::append(), which you use to append the newline, returns (), not the modified Vec.
Simple solution: modify the closure to return the Vec<u8>:
|c| {
let mut v = c.iter()
.map(|byte| byte.to_ascii_uppercase())
.collect::<Vec<u8>>();
v.append(&mut newline);
v
}
Since a newline is a one-byte character, it might make more sense to just add it to the Vec directly:
v.push(b'\n');
This avoids the unnecessary allocation of the newline Vec for every request.

How to split a String in Rust that I take as an input with read!()

I want to split a String that I give as an input according to white spaces in it.
I have used the split_whitespaces() function but when I use this function on a custom input it just gives me the first String slice.
let s:String = read!();
let mut i:usize = 0;
for token in s.split_whitespace() {
println!("token {} {}", i, token);
i+=1;
}
What am I missing?
As far as I know, read! is not a standard macro. A quick search reveals that is probably is from the text_io crate (if you are using external crates you should tell so in the question).
From the docs in that crate:
The read!() macro will always read until the next ascii whitespace character (\n, \r, \t or space).
So what you are seeing is by design.
If you want to read a whole line from stdin you may try the standard function std::Stdin::read_line.
You are missing test cases which could locate the source of the problem. Split the code into a function and replace the read!()-macro with a test case, which you could put in main for now, where you provide different strings to the function and observe the output.
fn strspilit(s:String){
let mut i:usize = 0;
for token in s.split_whitespace() {
println!("token {} {}", i, token);
i+=1;
}
}
fn main() {
println!("Hello, world!");
strspilit("Hello Huge World".to_string());
}
Then you will see your code is working as it should but as notices in other answers the read!() macro is only returning the string until the first white space so you should probably use another way of reading your input.

How can I append a char or &str to a String without first converting it to String?

I am attempting to write a lexer for fun, however something keeps bothering me.
let mut chars: Vec<char> = Vec::new();
let mut contents = String::new();
let mut tokens: Vec<&String> = Vec::new();
let mut append = String::new();
//--snip--
for _char in chars {
append += &_char.to_string();
append = append.trim().to_string();
if append.contains("print") {
println!("print found at: \n{}", append);
append = "".to_string();
}
}
Any time I want to do something as simple as append a &str to a String I have to convert it using .to_string, String::from(), .to_owned, etc.
Is there something I am doing wrong, so that I don't have to constantly do this, or is this the primary way of appending?
If you're trying to do something with a type, check the documentation. From the documentation for String:
push: "Appends the given char to the end of this String."
push_str: "Appends a given string slice onto the end of this String."
It's important to understand the differences between String and &str, and why different methods accept and return each of them.
A &str or &mut str are usually preferred in function arguments and return types. That's because they are just pointers to data so nothing needs to be copied or moved when they are passed around.
A String is returned when a function needs to do some new allocation, while &str and &mut str are slices into an existing String. Even though &mut str is mutable, you can't mutate it in a way that increases its length because that would require additional allocation.
The trim function is able to return a &str slice because that doesn't involve mutating the original string - a trimmed string is just a substring, which a slice perfectly describes. But sometimes that isn't possible; for example, a function that pads a string with an extra character would have to return a String because it would be allocating new memory.
You can reduce the number of type conversions in your code by choosing different methods:
for c in chars {
append.push(c); // append += &_char.to_string();
append = append.trim().to_string();
if append.contains("print") {
println!("print found at: \n{}", append);
append.clear(); // append = "".to_string();
}
}
There isn't anything like a trim_in_place method for String, so the way you have done it is probably the only way.

How to iterate through characters in a string in Rust to match words?

I'd like to iterate through a sentence to extract out simple words from the string. Here's what I have so far, trying to make the parse function first match world in the input string:
fn parse(input: String) -> String {
let mut val = String::new();
for c in input.chars() {
if c == "w".to_string() {
// guessing I have to test one character at a time
val.push_str(c.to_str());
}
}
return val;
}
fn main() {
let s = "Hello world!".to_string();
println!("{}", parse(s)); // should say "world"
}
What is the correct way to iterate through the characters in a string to match patterns in Rust (such as for a basic parser)?
Checking for words in a string is easy with the str::contains method.
As for writing a parser itself, I don't think it's any different in Rust than other languages. You have to create some sort of state machine.
For examples, you could check out serialize::json. I also wrote a CSV parser that uses a buffer with a convenient read_char method. The advantage of using this approach is that you don't need to load the whole input into memory at once.

Resources