I'm working on a simple Rust app that accepts stdin and acts on the basis of it. I'd like to have each command return a vector of results.
Different commands may return differently typed vectors; the list
method returns a vector of PathBuf
s, but the default match arm returns strings:
use std::{io, fs};
use std::path::PathBuf;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
let chars_to_trim: &[char] = &['\n'];
let trimmed_input: &str = input.trim_matches(chars_to_trim);
let no_match = vec!["No Match"];
let result = match trimmed_input {
"list" => list(),
_ => no_match,
};
}
fn list() -> Vec<PathBuf> {
println!("{}", "list of lockfiles here");
let entries = fs::read_dir("/tmp").expect("Failed to read /tmp");
let all: Result<_, _> = entries.map(|entry| entry.map(|e| e.path())).collect();
all.expect("Unable to read an entry")
}
This causes compilation to fail:
error[E0308]: match arms have incompatible types
--> src/main.rs:12:22
|
12 | let result = match trimmed_input {
| ^ expected struct `std::path::PathBuf`, found &str
|
= note: expected type `std::vec::Vec<std::path::PathBuf>`
= note: found type `std::vec::Vec<&str>`
note: match arm with an incompatible type
--> src/main.rs:14:18
|
14 | _ => no_match,
| ^^^^^^^^
What is the idiomatic Rust way to handle this? I've read through the documentation on generics but I'm not sure how to apply it.
Here's a reduced testcase:
use std::path::PathBuf;
fn main() {
let paths: Vec<PathBuf> = Vec::new();
let defaults: Vec<&'static str> = Vec::new();
let result = match 1 {
1 => paths,
_ => defaults,
};
}
As the error message is trying to say, Rust requires that a single variable will always have the same type. It simply doesn't make sense to have one variable be an unknown type out of a set.
The most straight-forward thing you can do is create an enum
that wraps both cases you have:
use std::path::PathBuf;
enum MyThing<'a> {
Str(&'a str),
Path(PathBuf),
}
fn main() {
let paths: Vec<PathBuf> = Vec::new();
let defaults: Vec<&'static str> = Vec::new();
let result: Vec<_> = match 1 {
1 => paths.into_iter().map(MyThing::Path).collect(),
_ => defaults.into_iter().map(MyThing::Str).collect(),
};
}
You could also just pick a middle ground and convert to that type, perhaps String
:
use std::path::PathBuf;
fn main() {
let paths: Vec<PathBuf> = Vec::new();
let defaults: Vec<&'static str> = Vec::new();
let result: Vec<_> = match 1 {
1 => paths.into_iter().map(|p| p.to_string_lossy().into_owned()).collect(),
_ => defaults.into_iter().map(|s| s.to_string()).collect(),
};
}
A third option is to create a trait and implement it for both types. You can then create a trait object. This option is closest to dynamic languages that you may be familiar with. It adds an extra layer of indirection, allowing for more flexibility:
use std::path::PathBuf;
trait MyTrait {
fn size(&self) -> u8;
}
impl MyTrait for PathBuf {
fn size(&self) -> u8 {
15
}
}
impl<'a> MyTrait for &'a str {
fn size(&self) -> u8 {
88
}
}
fn main() {
let paths: Vec<PathBuf> = Vec::new();
let defaults: Vec<&'static str> = Vec::new();
let result: Vec<_> = match 1 {
1 => paths.into_iter().map(|p| Box::new(p) as Box<MyTrait>).collect(),
_ => defaults.into_iter().map(|s| Box::new(s) as Box<MyTrait>).collect(),
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With