rustc false positive warning about value never read? - rust

Following this article about writing a shell in Rust. Compiler outputs a warning about a variable never being read.
use std::io::stdin;
use std::io::stdout;
use std::io::Write;
use std::process::Command;
use std::path::Path;
fn main(){
let mut input = String::new();
loop {
print!("> ");
stdout().flush().unwrap();
// input = String::from(""); // **<===== HERE**
stdin().read_line(&mut input).unwrap();
let mut parts = input.trim().split_whitespace();
let command = parts.next().unwrap();
let args = parts;
match command {
"cd" => {
let new_dir = args.peekable().peek().map_or("/", |x| *x);
let root = Path::new(new_dir);
if let Err(e) = std::env::set_current_dir(&root) {
eprintln!("{}", e);
}
},
"exit" => return,
command => {
let child = Command::new(command)
.args(args)
.spawn();
// gracefully handle malformed user input
match child {
Ok(mut child) => { child.wait().unwrap(); },
Err(e) => eprintln!("{}", e),
};
}
}
}
}
However, commenting out this line causes program to bug: here is the behavior before and after commenting:
usn#DESKTOP:~/bqy/rust/p2$ cargo run
warning: value assigned to `input` is never read
--> src/main.rs:8:10
|
8 | let mut input = String::new();
| ^^^^^
|
= note: `#[warn(unused_assignments)]` on by default
= help: maybe it is overwritten before being read?
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/p2`
> ls
Cargo.lock Cargo.toml src target
> ls
Cargo.lock Cargo.toml src target
> exit
usn#DESKTOP:~/bqy/rust/p2$ vi src/main.rs
usn#DESKTOP:~/bqy/rust/p2$ cargo run
Compiling p2 v0.1.0 (/mnt/d/MMM/projects/bqy/rust/p2)
Finished dev [unoptimized + debuginfo] target(s) in 1.13s
Running `target/debug/p2`
> ls
Cargo.lock Cargo.toml src target
> ls
ls: cannot access 'ls': No such file or directory
> exit
ls: cannot access 'ls': No such file or directory
ls: cannot access 'exit': No such file or directory
> ^C
usn#DESKTOP:~/bqy/rust/p2$ vi src/main.rs
Simple explanation of unwanted behavior is that command line is not resetted. So, why does the compiler complains ?
Thank you for reading.

Without the line to clear the buffer, its content is reused from one iteration to another. This is why your program doesn't work without
input = String::from("");
But this gives you a warning because you gave input a value when you declared it:
let mut input = String::new();
// ^^^^^^^^^^^^^^^^
and this value is not used, since literally the first use of input is to override it. So just don't give it a value:
let mut input;
In Rust this is perfectly safe and the compiler won't let you accidentally use input before you initialize it.
Alternatively, if you goal was to reuse the buffer to save on memory, you should use
input.clear();
because input = String::from(""); creates a completely new string with a new buffer and throws the other one away.

Related

How to remove 'temporary value dropped while borrowed' in a match

I'm trying a simple variation of command name and arguments depending on the OS. I have a common set of methods for the stdin/stdout, spawn and expect so I would like not to duplicate them since I may add other OS values later:
let cmd = match env::consts::OS {
"windows" =>
Command::new("cmd.exe").arg("/c").arg("dir"),
"linux" =>
Command::new("ls").arg("-l"),
_ => {
panic!("Unrecognized OS");
}
};
let proc = cmd
.stdout(Stdio::piped())
.spawn()
.expect("Cannot launch the process");
But I'm getting this error:
error[E0716]: temporary value dropped while borrowed
--> src\inouts.rs:37:13
|
35 | let cmd = match env::consts::OS {
| --- borrow later stored here
36 | "windows" =>
37 | Command::new("cmd.exe").arg("/c").arg("dir"),
| ^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
|
= note: consider using a `let` binding to create a longer lived value
I've tried to use intermediate variables in the match, or to declare cmd before like this:
let mut cmd: &Command;
match env::consts::OS {
"windows" => {
cmd = Command::new("cmd.exe").arg("/c").arg("dir")
}
...
but I can't seem to make it work without copying and duplicating the whole part with proc within the match. So this works but it start to get very long and repetitive:
let proc: Child;
match env::consts::OS {
"windows" => {
proc = Command::new("cmd.exe")
.arg("/c")
.arg("dir")
.stdout(Stdio::piped())
.spawn()
.expect("Cannot launch the process");
}
...
Is there a way to overcome the lifetime issue and keep something close to the initial code?
I've check many similar temporary value dropped while borrowed problems but couldn't find any solution.
The issue is that the function arg returns a &mut to its underlying Command, not the owned Command itself. So trying to return the return value of arg from a match arm does indeed return a reference to a Command that exists only within the match arm.
To work around this, you can create the Command, store it (owned) in a variable, and then apply the args.
let mut cmd;
match env::consts::OS {
"windows" => {
cmd = Command::new("cmd.exe");
cmd.arg("/c").arg("dir")
}
"linux" => {
cmd = Command::new("ls");
cmd.arg("-l")
}
_ => {
panic!("Unrecognized OS");
}
};
let proc = cmd
.stdout(Stdio::piped())
.spawn()
.expect("Cannot launch the process");

Handling piped data stdin with Rust

I'm having trouble with stdin in Rust. I'm trying to process stdin comming from a pipe on a linux terminal, something like grep for example.
echo "lorem ipsum" | grep <text>
Im using this in rust:
fn load_stdin() -> Result<String> {
let mut buffer = String::new();
let stdin = stdin();
stdin.read_line(&mut buffer)?;
return Ok(buffer);
}
But the problem is that if I don't bring in any piped data I get prompted to write, I would instead like to return Err.
So basically, if I do something like:
ls | cargo run
user#machine: ~ $
All is good. But if I do not pipe any stdin:
cargo run
The program halts and waits for user input.
You can use the atty crate to test whether your standard input is redirected:
use std::io;
use atty::Stream;
fn load_stdin() -> io::Result<String> {
if atty::is(Stream::Stdin) {
return Err(io::Error::new(io::ErrorKind::Other, "stdin not redirected"));
}
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
return Ok(buffer);
}
fn main() -> io::Result<()> {
println!("line: {}", load_stdin()?);
Ok(())
}
This results in the desired behavior:
$ echo "lorem ipsum" | cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/playground`
line: lorem ipsum
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/playground`
Error: Custom { kind: Other, error: "stdin not redirected" }

Read lines from std input using Rust

Hi I want to be able to read a file which contains json lines into a rust app like this
$ cargo run < users.json
and then read those lines as an iterator. As of now I have this code but i don't want the file hard coded but piped into the process as in the line above.
use std::fs::File;
use std::io::{self, prelude::*, BufReader};
fn main() -> io::Result<()> {
let file = File::open("users.json")?;
let reader = BufReader::new(file);
for line in reader.lines() {
println!("{}", line);
}
Ok(())
}
I just solved it this makes the trick
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
println!("{}", line.unwrap());
}
}
cargo help run reveals:
NAME
cargo-run - Run the current package
SYNOPSIS
cargo run [options] [-- args]
DESCRIPTION
Run a binary or example of the local package.
All the arguments following the two dashes (--) are passed to the binary to run. If you're passing arguments to both Cargo and the binary, the ones after -- go to the binary, the ones before go to Cargo.
So to pass arguments the syntax would be:
cargo run -- foo bar baz
You can then access the values like this:
let args: Vec<String> = env::args().collect();
A complete minimal example would be:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(&args);
}
Running cargo run -- users.json would result in:
$ cargo run -- users.json
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/sandbox users.json`
[src/main.rs:5] &args = [
"target/debug/sandbox",
"users.json",
]
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
println!("{}", line.unwrap());
}
}

Creates a temporary which is freed while still in use

I'm creating a small application that explores variable lifetimes and threads. I want to load in a file once, and then use its contents (in this case an audio file) in a separate channel. I am having issues with value lifetimes.
I'm almost certain the syntax is wrong for what I have so far (for creating a static variable), but I can't find any resources for File types and lifetimes. What I have thus far produces this error:
let file = &File::open("src/censor-beep-01.wav").unwrap();
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
let x: &'static File = file;
------------- type annotation requires that borrow lasts for `'static`
The code I currently have is:
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_must_use)]
#![allow(unused_variables)]
use std::io::{self, BufRead, BufReader, stdin, Read};
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use std::time::Duration;
use std::fs::File;
use std::rc::Rc;
use rodio::Source;
fn main() {
let file = &File::open("src/censor-beep-01.wav").unwrap();
let x: &'static File = file;
loop {
let (tx, rx) = mpsc::channel();
thread::spawn(move || loop {
let tmp = x;
let (stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let source = rodio::Decoder::new(BufReader::new(tmp)).unwrap();
stream_handle.play_raw(source.convert_samples());
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => {
break;
}
Err(TryRecvError::Empty) => {
println!("z");
thread::sleep(Duration::from_millis(1000));
}
}
});
let mut line = String::new();
let stdin = io::stdin();
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(());
return;
}
}
You need to wrap the file with Arc and Mutex like Arc::new(Mutex::new(file)) and then clone the file before passing it to the thread.
Arc is used for reference counting, which is needed to share the target object (in your case it is a file) across the thread and Mutex is needed to access the target object synchronously.
sample code (I have simplified your code to make it more understandable):
let file = Arc::new(Mutex::new(File::open("src/censor-beep-01.wav").unwrap()));
loop {
let file = file.clone();
thread::spawn(move || loop {
let mut file_guard = match file.lock() {
Ok(guard) => guard,
Err(poison) => poison.into_inner()
};
let file = file_guard.deref();
// now you can pass above file object to BufReader like "BufReader::new(file)"
});
}
reason for creates a temporary which is freed while still in use error:
You have only stored the reference of the file without the actual file object. so, the object will be droped in that line itself.

How to capture the output of a process piped into a Rust program?

I know how to read the command line arguments, but I am having difficulties reading the command output from a pipe.
Connect a program (A) that outputs data to my Rust program using a pipe:
A | R
The program should consume the data line by line as they come.
$ pwd | cargo run should print the pwd output.
OR
$ find . | cargo run should output the find command output which is more than 1 line.
Use BufRead::lines on a locked handle to standard input:
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line.expect("Could not read line from standard in");
println!("{}", line);
}
}
If you wanted to reuse the allocation of the String, you could use the loop form:
use std::io::{self, Read};
fn main() {
let stdin = io::stdin();
let mut stdin = stdin.lock(); // locking is optional
let mut line = String::new();
// Could also `match` on the `Result` if you wanted to handle `Err`
while let Ok(n_bytes) = stdin.read_to_string(&mut line) {
if n_bytes == 0 { break }
println!("{}", line);
line.clear();
}
}
You just need to read from Stdin.
This is based on an example taken from the documentation:
use std::io;
fn main() {
loop {
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(len) => if len == 0 {
return;
} else {
println!("{}", input);
}
Err(error) => {
eprintln!("error: {}", error);
return;
}
}
}
}
It's mostly the docs example wrapped in a loop, breaking out of the loop when there is no more input, or if there is an error.
The other changes is that it's better in your context to write errors to stderr, which is why the error branch uses eprintln!, instead of println!. This macro probably wasn't available when that documentation was written.
use std::io;
fn main() {
loop {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("failed to read from pipe");
input = input.trim().to_string();
if input == "" {
break;
}
println!("Pipe output: {}", input);
}
}
OUTPUT:
[18:50:29 Abhinickz#wsl -> pipe$ pwd
/mnt/d/Abhinickz/dev_work/learn_rust/pipe
[18:50:46 Abhinickz#wsl -> pipe$ pwd | cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/pipe`
Pipe output: /mnt/d/Abhinickz/dev_work/learn_rust/pipe
You can do it in a pretty snazzy and concise way with rust's iterator methods
use std::io::{self, BufRead};
fn main() {
// get piped input
// eg `cat file | ./program`
// ( `cat file | cargo run` also works )
let input = io::stdin().lock().lines().fold("".to_string(), |acc, line| {
acc + &line.unwrap() + "\n"
});
dbg!(input);
}

Resources