Возврат и использование универсального типа с соответствием

Я работаю над простым приложением Rust, которое принимает стандартный ввод и действует на его основе. Я хотел бы, чтобы каждая команда возвращала вектор результатов.

Различные команды могут возвращать разные типы векторов; list метод возвращает вектор PathBufs, но рука соответствия по умолчанию возвращает строки:

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

Это приводит к сбою компиляции:

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,
   |                  ^^^^^^^^

Каков идиоматический способ Rust справиться с этим? Я прочитал документацию по дженерикам, но я не уверен, как ее применять.

1 ответ

Решение

Вот сокращенный тестовый пример:

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

Как говорится в сообщении об ошибке, Rust требует, чтобы одна переменная всегда имела одинаковый тип. Просто не имеет смысла иметь одну переменную неизвестного типа из набора.

Самое простое, что вы можете сделать, это создать enum это охватывает оба случая:

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

Вы также можете просто выбрать золотую середину и перейти к этому типу, возможно, 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(),
    };
}

Третий вариант - создать черту и реализовать ее для обоих типов. Затем вы можете создать объект черты. Этот параметр наиболее близок к динамическим языкам, с которыми вы, возможно, знакомы. Это добавляет дополнительный уровень косвенности, обеспечивая большую гибкость:

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(),
    };
}
Другие вопросы по тегам