Rust 如何使变量在不同的条件下具有不同的类型


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)
}

当未提供命令行参数时output_path应stdout()writer,在提供命令行参数时应Fileoutput_path。 此代码不起作用,因为无法在编译时确定writer的类型。

我把它改成下面。由于FileStdout都实现了std::io::Write,我需要的是对std::io::Write的引用。由于函数run的签名是:

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)
}

但它也不起作用。它有终身问题。

然后我把它改成:

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()}) )
}

它与第一段代码有相同的问题。

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

那么,如何在未提供命令行参数时output_pathwriterstdout(),而在提供命令行参数时Fileoutput_path呢?

正如你所指出的,你需要writer成为对std::io::Write的某种引用,但你也需要有人拥有相应的值。这里显而易见的解决方案是使用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)
}

操场

我想你可能想阅读更多关于如何使用特质的信息。

下面是一个示例代码,可以做你想要的:

#[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();
}

你可以在这里尝试

为了避免动态调度的开销,可以使用eithercrate 中的Either类型来包装值,如果可能的话,它会自动实现标准库中的所有(大多数?)特征:

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()),
};

(游乐场)

在这里,在我看来,使用match表达在概念上更清晰。


在不使用外部板条箱的情况下,还可以使用变量模拟Either枚举的行为:

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
}
};

(游乐场)

最新更新