How do I convert from &alloc::string::String to a string literal? - rust

The title says it all really. I need to convert from &alloc::string::String to a string literal (&str I think) according to the error I am getting when I am trying to write to a file. How do I convert what I have into that?
The overall goal here is to read from one file and line by line append to another.
Full code:
use std::{
fs::File,
io::{self, BufRead, BufReader},
fs::OpenOptions,
fs::write,
any::type_name,
path::Path,
io::Write,
};
fn type_of<T>(_: T) -> &'static str {
type_name::<T>()
}
fn main(){
let inpath = Path::new("tool_output.txt");
let outpath = Path::new("test_output.txt");
let indisplay = inpath.display();
let outdisplay = outpath.display();
let mut infile = match File::open(&inpath) {
Err(why) => panic!("couldn't open {}: {}", indisplay, why),
Ok(infile) => infile,
};
let mut outfile = match OpenOptions::new().write(true).append(true).open(&outpath) {
Err(why) => panic!("couldn't open {}: {}", outdisplay, why),
Ok(outfile) => outfile,
};
let reader = BufReader::new(infile);
for line in reader.lines() {
let format_line = String::from(line.unwrap()); // <- I thought this would fix the error but it didnt.
println!("Type = {}", type_of(&format_line));
let _ = writeln!(outfile, &format_line).expect("Unable to write to file"); <- this is currently causing the error.
//write("test_output.txt", line.unwrap()).expect("Unable to write to file");
}
}
error:
error: format argument must be a string literal
--> text_edit.rs:36:28
|
36 | let _ = writeln!(outfile, format_line).expect("Unable to write to file");
| ^^^^^^^^^^^
|

A string literal is what it says - a literal so "literal" is a string literal. To use writeln macro to write a string, you have to do writeln!(outfile, "{}", line) and here "{}" is the format string literal. If you’ve ever used println macro, it’s basically that but you specify what stream to print to.

Related

How to read a text File in Rust and read mutliple Values per line

So basically, I have a text file with the following syntax:
String int
String int
String int
I have an idea how to read the Values if there is only one entry per line, but if there are multiple, I do not know how to do it.
In Java, I would do something simple with while and Scanner but in Rust I have no clue.
I am fairly new to Rust so please help me.
Thanks for your help in advance
Solution
Here is my modified Solution of #netwave 's code:
use std::fs;
use std::io::{BufRead, BufReader, Error};
fn main() -> Result<(), Error> {
let buff_reader = BufReader::new(fs::File::open(file)?);
for line in buff_reader.lines() {
let parsed = sscanf::scanf!(line?, "{} {}", String, i32);
println!("{:?}\n", parsed);
}
Ok(())
}
You can use the BuffRead trait, which has a read_line method. Also you can use lines.
For doing so the easiest option would be to wrap the File instance with a BuffReader:
use std::fs;
use std::io::{BufRead, BufReader};
...
let buff_reader = BufReader::new(fs::File::open(path)?);
loop {
let mut buff = String::new();
buff_reader.read_line(&mut buff)?;
println!("{}", buff);
}
Playground
Once you have each line you can easily use sscanf crate to parse the line to the types you need:
let parsed = sscanf::scanf!(buff, "{} {}", String, i32);
Based on: https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html
For data.txt to contain:
str1 100
str2 200
str3 300
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
fn main() {
// File hosts must exist in current path before this produces output
if let Ok(lines) = read_lines("./data.txt") {
// Consumes the iterator, returns an (Optional) String
for line in lines {
if let Ok(data) = line {
let values: Vec<&str> = data.split(' ').collect();
match values.len() {
2 => {
let strdata = values[0].parse::<String>();
let intdata = values[1].parse::<i32>();
println!("Got: {:?} {:?}", strdata, intdata);
},
_ => panic!("Invalid input line {}", data),
};
}
}
}
}
// The output is wrapped in a Result to allow matching on errors
// Returns an Iterator to the Reader of the lines of the file.
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
Outputs:
Got: Ok("str1") Ok(100)
Got: Ok("str2") Ok(200)
Got: Ok("str3") Ok(300)

Why does my line from BufReader::lines not match?

I am trying to parse a text file and find if a string is equal to current line I am on using BufReader:
let mut chapter = String::from("Chapter 1\n");
//open file
let file = File::open("document.txt")?;
let reader = BufReader::new(file);
for line in reader.lines() {
if chapter.eq(&line.unwrap()) {
println!("chapter found!");
}
}
However, the if statement never returns true. How can I properly convert line from reader.lines() into a way I can find a match?
From the documentation of ReadBuf::lines:
Each string returned will not have a newline byte (the 0xA byte) or CRLF (0xD, 0xA bytes) at the end.
Remove the \n from your search string:
let mut chapter = String::from("Chapter 1");
There's no reason to allocate the String here. A number of other changes:
use std::{
fs::File,
io::{self, BufRead, BufReader},
};
fn example() -> io::Result<()> {
let chapter = "Chapter 1";
let file = File::open("document.txt")?;
let reader = BufReader::new(file);
for line in reader.lines() {
if chapter == line.unwrap() {
println!("chapter found!");
}
}
Ok(())
}
See also:
Why does my string not match when reading user input from stdin?

How to get the result of read_line() as a String in Rust?

I'm trying to get a String from standard input:
use std::io;
fn ask_nick() -> String {
let reader = io::stdin();
let mut buffer: String = String::new();
let nickname: String = reader.read_line(&mut buffer).ok()
.expect("ERRMSG").to_string();
println!("OK: Hello {}!", &nickname);
return nickname;
}
}
fn main() {
let nickname: String = ask_nick();
println!("{}", nickname);
}
But conversion from usize to String seems to change contents to it's length:
INPUT:= John
EXPECTED OUTPUT:= OK: Hello John!
John
OUTPUT:= OK: Hello 5!
5
INPUT:= Doe
EXPECTED OUTPUT:= OK: Hello Doe!
Doe
OUTPUT:= OK: Hello 4!
4
Please see the documentation, and you can see that read_line mutates the contents of its parameter (in your case, the empty string bound at buffer), putting the value read into it, and returns the length read. But you're unwrapping that result and converting that length to a string.
Instead, your function should look like:
fn ask_nick() -> String {
let reader = io::stdin();
let mut buffer: String = String::new();
reader.read_line(&mut buffer)
.ok()
.expect("ERRMSG");
println!("OK: Hello {}!", buffer);
return buffer;
}
Or, even more idiomatically, not panicking when invalid input is provided:
fn ask_nick() -> Result<String> {
let reader = io::stdin();
let mut buffer: String = String::new();
match reader.read_line(&mut buffer) {
Ok(_) => Ok(buffer),
Err(e) => Err(e),
}
}
In this variant, the caller is the one that decides how to handle errors.

read file(not utf-8) line by line?

Is it possible to read file line by line, if it is not in utf-8 encoding with std::io::File and std::io::BufReader?
I look at std::io::Lines and it return Result<String>, so
I worry, have I implement my own BufReader that do the same, but return Vec<u8> instead, or I can reuse std::io::BufReader in some way?
You do not have to re-implement BufReader itself, it provides exactly the method you need for your usecase read_until:
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize>
You supply your own Vec<u8> and the content of the file will be appended until byte is encountered (0x0A being LF).
There are several potential gotchas:
the buffer may end not only with a LF byte, but with a CR LF sequence,
it is up to you to clear buf between subsequent calls.
A simple while let Ok(_) = reader.read_until(0x0A as u8, buffer) should let you read your file easily enough.
You may consider implement a std::io::Lines equivalent which converts from the encoding to UTF-8 to provide a nice API, though it will have a performance cost.
To read files (utf-8 or not utf-8) line by line, I use:
/*
// Cargo.toml:
[dependencies]
encoding_rs = "0.8"
encoding_rs_io = "0.1.7"
...
*/
use encoding_rs::WINDOWS_1252;
use encoding_rs_io::DecodeReaderBytesBuilder;
use std::{
fs,
io::{Read, BufRead, BufReader, Error},
};
fn main() {
let file_path: &str = "/tmp/file.txt";
let buffer: Box<dyn BufRead> = read_file(file_path);
for (index, result_vec_bytes) in buffer.split(b'\n').enumerate() {
let line_number: usize = index + 1;
let line_utf8: String = get_string_utf8(result_vec_bytes, line_number, file_path);
println!("{line_utf8}");
}
}
Such that:
fn read_file(file_path: &str) -> Box<dyn BufRead> {
let file = match fs::File::open(file_path) {
Ok(f) => f,
Err(why) => panic!("Problem opening the file: \"{file_path}\"\n{why:?}"),
};
Box::new(BufReader::new(file))
}
And
fn get_string_utf8(result_vec_bytes: Result<Vec<u8>, std::io::Error>, line_number: usize) -> String {
let vec_bytes: Vec<u8> = match result_vec_bytes {
Ok(values) => values,
Err(why) => panic!("Failed to read line nº {line_number}: {why}"),
};
// from_utf8() checks to ensure that the bytes are valid UTF-8
let line_utf8: String = match std::str::from_utf8(&vec_bytes) {
Ok(str) => str.to_string(),
Err(_) => {
let mut data = DecodeReaderBytesBuilder::new()
.encoding(Some(WINDOWS_1252))
.build(vec_bytes.as_slice());
let mut buffer = String::new();
let _number_of_bytes = match data.read_to_string(&mut buffer) {
Ok(num) => num,
Err(why) => {
eprintln!("Problem reading data from file in buffer!");
eprintln!("Line nº {line_number}");
eprintln!("Used encoding type: WINDOWS_1252.");
eprintln!("Try another encoding type!");
panic!("Failed to convert data to UTF-8!: {why}")
},
};
buffer
}
};
// remove Window new line: "\r\n"
line_utf8.trim_end_matches('\r').to_string()
}

rust create a String from file.read_to_end()

I am trying to read a file and return it as a UTF-8 std:string:String it seems like content is a Result<collections::string::String, collections::vec::Vec<u8>> if i understand an error message i got from trying String::from_utf8(content).
fn get_index_body () -> String {
let path = Path::new("../html/ws1.html");
let display = path.display();
let mut file = match File::open(&path) {
Ok(f) => f,
Err(err) => panic!("file error: {}", err)
};
let content = file.read_to_end();
println!("{} {}", display, content);
return String::new(); // how to turn into String (which is utf-8)
}
Check the functions provided by the io::Reader trait: http://doc.rust-lang.org/std/io/trait.Reader.html
read_to_end() returns IoResult<Vec<u8>>, read_to_string() returns IoResult<String>.
IoResult<String> is just a handy way to write Result<String, IoError>: http://doc.rust-lang.org/std/io/type.IoResult.html
You can extract Strings from a Result either using unwrap():
let content = file.read_to_end();
content.unwrap()
or by handling the error yourself:
let content = file.read_to_end();
match content {
Ok(s) => s,
Err(why) => panic!("{}", why)
}
See also: http://doc.rust-lang.org/std/result/enum.Result.html

Resources