Reputation: 470
fn main() -> std::io::Result<()> {
let path = std::env::args().nth(1).expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
let writer;
if output_path.is_none() {
writer = std::io::stdout();
} else {
writer = std::fs::File::open(output_path.unwrap()).unwrap();
}
minidecaf::run(&input, &mut writer)
}
writer
should be stdout()
when output_path command line argument is not provided and be File
when output_path command line argument is provided.
This code does not work, because the type of writer
cannot be determined in compile time.
I change it to the following. Since both File
and Stdout
implements std::io::Write
, and what I need is a reference to std::io::Write
. As the signature of function run
is:
pub fn run(input: &str, output: &mut impl std::io::Write) -> std::io::Result<()>
fn main() -> std::io::Result<()> {
let path = std::env::args().nth(1).expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
let mut writer : &mut std::io::Write;
if output_path.is_none() {
writer = &mut std::io::stdout();
} else {
writer = &mut std::fs::File::open(output_path.unwrap()).unwrap();
}
minidecaf::run(&input, &mut writer)
}
But it does not work, too. It has lifetime problems.
Then I change it to:
fn main() -> std::io::Result<()> {
let path = std::env::args().nth(1).expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
minidecaf::run(&input, &mut (if output_path.is_none() {std::io::stdout()} else {std::fs::File::open(output_path.unwrap()).unwrap()}) )
}
It has the same problem with the first piece of code.
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:11:83
|
11 | minidecaf::run(&input, &mut (if output_path.is_none() {std::io::stdout()} else {std::fs::File::open(output_path.unwrap()).unwrap()}) )
| ----------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Stdout`, found struct `File`
| |
| expected because of this
So how can I make writer
be stdout()
when output_path command line argument is not provided and be File
when output_path command line argument is provided?
Upvotes: 0
Views: 332
Reputation: 23443
As you noted, you need writer
to be some kind of reference to std::io::Write
, but you also need someone to own the corresponding value. The obvious solution here is to use a Box
:
fn run(input: &str, output: &mut impl std::io::Write) -> std::io::Result<()> {
unimplemented!();
}
fn main() -> std::io::Result<()> {
let path = std::env::args()
.nth(1)
.expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
let mut writer: Box<dyn std::io::Write>;
if output_path.is_none() {
writer = Box::new(std::io::stdout());
} else {
writer = Box::new(std::fs::File::open(output_path.unwrap()).unwrap());
}
run(&input, &mut writer)
}
Upvotes: 2
Reputation: 20639
To avoid the overhead of dynamic dispatch, it is possible to use the Either
type from the either
crate to wrap the value, which automatically implements all (most?) traits from the standard library if possible:
use {
either::Either,
std::{fs::File, io},
};
let mut writer = match output_path {
Some(path) => Either::Left(File::open(path)?),
None => Either::Right(io::stdout()),
};
Here, using a match
expression is conceptually clearer in my opinion.
Without using external crates, it is also possible to mimic the behavior of an Either
enum using variables:
use std::{env, fs::File, io};
let (mut stdout, mut file);
let writer: &mut dyn io::Write = match output_path {
Some(path) => {
file = File::open(path)?;
&mut file
}
None => {
stdout = io::stdout();
&mut stdout
}
};
Upvotes: 1
Reputation: 535
I think you may want to read more about how to use trait.
Here is a sample code may do what you want:
#[derive(Default)]
struct A {
a: usize,
}
#[derive(Default)]
struct B {
b: usize,
}
trait C {
fn test(&self);
}
impl C for A {
fn test(&self) {
println!("A");
}
}
impl C for B {
fn test(&self) {
println!("B");
}
}
fn main() {
let t: Box<dyn C> = if true {
Box::new(A::default())
} else {
Box::new(B::default())
};
t.test();
}
You can try it Here
Upvotes: 1